9

in Python 3.6, I use unittest.mock.patch to patch a function like this:

class SampleTest(TestCase):

    @mock.patch('some_module.f')
    def test_f(self, mocked_f):
        f()
        mocked_f.assert_called()

This passes a mock.MagicMock() as mocked_f and everything works fine. However, when I want to use a custom mock object instead of the default mock.MagicMock() using new argument, the patch decorator does not pass the mocked object to the test_f method. Running this code will raise a TypeError:

class SampleTest(TestCase):

    @mock.patch('some_module.f', new=lambda: 8)
    def test_f(self, mocked_f):
        f()
        mocked_f.assert_called()
TypeError: test_f() missing 1 required positional argument: 'mocked_f'

My question is: why is this happening?

Reza
  • 1,065
  • 1
  • 10
  • 18

2 Answers2

8

I think it's probably correct that the reason the mocked object is not passed to the decorated function when new is specified, is that you will normally already have a reference to the that object, and so don't need it passed into the decorated function.

Notice however that if you use new_callable instead of new then the mocked object is passed to the decorated function. This makes sense since you normally won't have a reference to the object returned from the callable.

so you can do something like the following:

def my_custom_mock_factory():
    my_mock = mock.Mock()
    # customisations to my_mock
    return my_mock

class SampleTest(TestCase):

    @mock.patch('some_module.f', new_callable=my_custom_mock_factory)
    def test_f(self, mocked_f):
        f()
        mocked_f.assert_called()
tim-mccurrach
  • 6,395
  • 4
  • 23
  • 41
6

From the documentation (emphasis mine):

If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function.

With new being used explicitly, the decorator does not pass the mocked object as a parameter (presumably because it expects you to already have a reference that you could use without needing an argument).

In this case, a workaround would be to configure the mock after it has been passed to your test:

class SampleTest(TestCase):

    @mock.patch('tests.f')
    def test_f(self, mocked_f):
        mocked_f.return_value = 8
        # or
        # mocked_f.side_effect = lambda: 8
        f()
        mocked_f.assert_called()
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thanks for the answer. I had noticed the documentation you mentioned before, but I am not convinced with this presumption. Your solution perfectly fits for my example, but there are situations where `return_value` or `side_effect` does not provide a convenient workaround. This is the blog I was reading when I encountered the problem: https://blog.miguelgrinberg.com/post/unit-testing-asyncio-code I could update my question with the example in the blog, but it is too long. – Reza Mar 16 '19 at 14:29