@Patch decorator is not compatible with pytest fixture

Question:

I have encountered something mysterious, when using patch decorator from mock package integrated with pytest fixture.

I have two modules:

    -----test folder
          -------func.py
          -------test_test.py

in func.py:

    def a():
        return 1

    def b():
        return a()     

in test_test.py:

    import pytest
    from func import a,b
    from mock import patch,Mock

    @pytest.fixture(scope="module")
    def brands():
        return 1


    mock_b=Mock()

    @patch('test_test.b',mock_b)
    def test_compute_scores(brands):                 
         a()

It seems that patch decorate is not compatible with pytest fixture. Does anyone have a insight on that? Thanks

Asked By: Hello lad

||

Answers:

I had the same problem and solution for me was to use mock library in 1.0.1 version (before I was using unittest.mock in 2.6.0 version). Now it works like a charm 🙂

Answered By: Konrad Kocik

This doesn’t address your question directly, but there is the pytest-mock plugin which allows you to write this instead:

def test_compute_scores(brands, mock):                 
     mock_b = mock.patch('test_test.b')
     a()
Answered By: Bruno Oliveira

Hopefully this answer on an old question will help someone.

First off, the question doesn’t include the error, so we don’t really know what’s up. But I’ll try to provide something that helped me.

If you want a test decorated with a patched object, then in order for it to work with pytest you could just do this:

@mock.patch('mocked.module')
def test_me(*args):
    mocked_module = args[0]

Or for multiple patches:

@mock.patch('mocked.module1')
@mock.patch('mocked.module')
def test_me(*args):
    mocked_module1, mocked_module2 = args

pytest is looking for the names of the fixtures to look up in the test function/method. Providing the *args argument gives us a good workaround the lookup phase. So, to include a fixture with patches, you could do this:

# from question
@pytest.fixture(scope="module")
def brands():
    return 1

@mock.patch('mocked.module1')
def test_me(brands, *args):
    mocked_module1 = args[0]

This worked for me running python 3.6 and pytest 3.0.6.

Answered By: 404

As of Python3.3, the mock module has been pulled into the unittest library. There is also a backport (for previous versions of Python) available as the standalone library mock.

Combining these 2 libraries within the same test-suite yields the above-mentioned error:

E       fixture 'fixture_name' not found

Within your test-suite’s virtual environment, run pip uninstall mock, and make sure you aren’t using the backported library alongside the core unittest library. When you re-run your tests after uninstalling, you would see ImportErrors if this were the case.

Replace all instances of this import with from unittest.mock import <stuff>.

Answered By: The Aelfinn

When using pytest fixture with mock.patch, test parameter order is crucial.

If you place a fixture parameter before a mocked one:

from unittest import mock

@mock.patch('my.module.my.class')
def test_my_code(my_fixture, mocked_class):

then the mock object will be in my_fixture and mocked_class will be search as a fixture:

fixture 'mocked_class' not found

But, if you reverse the order, placing the fixture parameter at the end:

from unittest import mock

@mock.patch('my.module.my.class')
def test_my_code(mocked_class, my_fixture):

then all will be fine.

Answered By: bux

If you have multiple patches to be applied, order they are injected is important:

# from question
@pytest.fixture(scope="module")
def brands():
    return 1

# notice the order
@patch('my.module.my.class1')
@patch('my.module.my.class2')
def test_list_instance_elb_tg(mocked_class2, mocked_class1, brands):
    pass
Answered By: codarrior

a) For me the solution was to use a with block inside the test function instead of using a @patch decoration before the test function:

class TestFoo:

    def test_baa(self, my_fixture):
        with patch(
            'module.Class.function_to_patch',
            MagicMock(return_value='mocked_result')
        ) as mocked_function_to_patch:
            result= my_fixture.baa('mocked_input')
            assert result == 'mocked_result'
            mocked_function_to_patch.assert_has_calls([
                call('mocked_input')
            ])

This solution does work inside classes (that are used to structure/group my test methods). Using the with block, you don’t need to worry about the order of the arguments. I find it more explicit then the injection mechanism but the code becomes ugly if you patch more then one variable. If you need to patch many dependencies, that might be a signal that your tested function does too many things and that you should refactor it, e.g. by extracting some of the functionality to extra functions.

b) If you are outside classes and do want a patched object to be injected as extra argument in a test method… please note that @patch does not support to define the mock as second argument of the decoration:

@patch('path.to.foo', MagicMock(return_value='foo_value')) 
def test_baa(self, my_fixture, mocked_foo):

does not work.

=> Make sure to pass the path as only argument to the decoration. Then define the return value inside the test function:

@patch('path.to.foo') 
def test_baa(self, my_fixture, mocked_foo):
    mocked_foo.return_value = 'foo_value'

(Unfortunately, this does not seem to work inside classes.)

First let inject the fixture(s), then let inject the variables of the @patch decorations (e.g. ‘mocked_foo’).

The name of the injected fixture ‘my_fixture’ needs to be correct. It needs to match the name of the decorated fixture function (or the explicit name used in the fixture decoration).

The name of the injected patch variable ‘mocked_foo’ does not follow a distinct naming pattern. You can choose it as you like, independent from the corresponding path of the @patch decoration.

If you inject several patched variables, note that the order is reversed: the mocked instance belonging to the last @patch decoration is injected first:

@patch('path.to.foo') 
@patch('path.to.qux') 
def test_baa(self, my_fixture, mocked_qux, mocked_foo):
    mocked_foo.return_value = 'foo_value'
Answered By: Stefan
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.