Search code examples
pythonunit-testingpytestpython-unittest

Unittest mock: return function which receives one pytest fixture and one argument


I'm creating some unit tests and using pytest fixtures in combination with some unittest.mock patch.object calls.

I would like to reuse a function that is called by some of my tests. It makes use of a pytest fixture (specified as the first "argument" of the function) and it requires an additional argument. It looks something like this:

import pandas as pd
import pytest
import os
from unittest.mock import patch

@pytest.fixture()
def rootdir():
    return os.path.dirname(os.path.abspath(__file__))

def my_mock_ret(rootdir, number):
    print(f"{rootdir}_{number}")
    return f"{rootdir}_{number}"

def test_simple(rootdir):
    a = pd.DataFrame()
    with patch.object(a, 'to_csv', lambda x: my_mock_ret(rootdir, x)) as _:
        a.to_csv(rootdir)

The tricky part is how to pass the number argument to my_mock_ret while also being able to access the rootdir fixture from inside it.

I've tried this way using lambda but it does not work.

Edit

It works if y put my_mock_ret inside test_simple, but I don't want to do that because I want to reuse my_mock_ret for several other tests:

import pandas as pd
import pytest
import os
from unittest.mock import patch

@pytest.fixture()
def rootdir():
    return os.path.dirname(os.path.abspath(__file__))

def test_simple(rootdir):
    def my_mock_ret(number):
        print(f"{rootdir}_{number}")
        return f"{rootdir}_{number}"

    a = pd.DataFrame()
    with patch.object(a, 'to_csv', my_mock_ret) as _:
        a.to_csv(rootdir)

Solution

  • What you need right here, is the factory pattern:

    import pandas as pd
    import pytest
    import os
    from unittest.mock import patch
    
    @pytest.fixture()
    def rootdir():
        return os.path.dirname(os.path.abspath(__file__))
    
    @pytest.fixture()
    def my_mock_ret(rootdir):
        def _my_mock_ret(number):
            print(f"{rootdir}_{number}")
            return f"{rootdir}_{number}"
        return _my_mock_ret
    
    def test_simple(my_mock_ret, rootdir):
        a = pd.DataFrame()
        with patch.object(a, 'to_csv', my_mock_ret) as _:
            a.to_csv(rootdir)
    

    here my_mock_ret will create a function, which captures rootdir, and can take the number argument.