159

I have a python file a.py which contains two classes A and B.

class A(object):
    def method_a(self):
        return "Class A method a"

class B(object):
    def method_b(self):
        a = A()
        print a.method_a()

I would like to unittest method_b in class B by mocking A. Here is the content of the file testa.py for this purpose:

import unittest
import mock
import a


class TestB(unittest.TestCase):

    @mock.patch('a.A')
    def test_method_b(self, mock_a):
        mock_a.method_a.return_value = 'Mocked A'
        b = a.B()
        b.method_b()


if __name__ == '__main__':
    unittest.main()

I expect to get Mocked A in the output. But what I get is:

<MagicMock name='A().method_a()' id='4326621392'>

Where am I doing wrong?

Mehdi Jafarnia Jahromi
  • 2,017
  • 3
  • 15
  • 14
  • 1
    When testing, `A()` returns the `return_value` from `mock_A` (a regular `MagicMock`, as you haven't specified anything else), which is not an instance of the class `A`. You need to set that `return_value` to be something that has a defined `method_a`. – jonrsharpe Jul 05 '16 at 08:52
  • 4
    mock_a.method_a.return_value = 'Mocked A' => mock_a().method_a.return_value = 'Mocked A' should be better :) – Ali SAID OMAR Jul 05 '16 at 08:53
  • @AliSAIDOMAR is precisely correct, it's the return value from calling `mock_a` that should have the method, not `mock_a` itself. – jonrsharpe Jul 05 '16 at 08:54
  • 2
    @jonrsharpe . Thanks for your explanation. I just tried. Both `mock_a().method_a.return_value = 'Mocked A'` and `mock_a.return_value.method_a.return_value = 'Mocked A'` worked. Thanks a lot for your comments. Would you please go ahead and put it as an answer? – Mehdi Jafarnia Jahromi Jul 05 '16 at 08:59
  • @MehdiJafarniaJahromi thanks a lot! – Niakros May 07 '18 at 16:32
  • @Niakros, you're welcome :) – Mehdi Jafarnia Jahromi May 07 '18 at 22:20
  • could something like this also work ? `mocker.patch('a.A.some_method_a', RETURN_VALUE="foo")` that's what im stuck on at the moment. seems that it still swaps magic mock for class A – Sonic Soul Feb 17 '20 at 18:09

3 Answers3

198

When you @mock.patch('a.A'), you are replacing the class A in the code under test with mock_a.

In B.method_b you then set a = A(), which is now a = mock_a() - i.e. a is the return_value of mock_a. As you haven't specified this value, it's a regular MagicMock; this isn't configured either, so you get the default response (yet another MagicMock) when calling methods on it.

Instead, you want to configure the return_value of mock_a to have the appropriate method, which you can do as either:

mock_a().method_a.return_value = 'Mocked A' 
    # ^ note parentheses

or, perhaps more explicitly:

mock_a.return_value.method_a.return_value = 'Mocked A'

Your code would have worked in the case a = A (assigning the class, not creating an instance), as then a.method_a() would have triggered your mock method.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • @imsrgadich do you need a *mock* for that? Just build the appropriate dataframe, treat it as a test value. – jonrsharpe Sep 08 '18 at 09:54
  • @jonrsharpe yeah, I could do that but I'm also performing df.drop in my called method which I need to assert and over that I'm not returning the dataframe back from called method. That creates a problem. I found a way using `mock_data.configure_mock(columns='my_column')` which solves it. Thanks for the reply though. (ref: https://bradmontgomery.net/blog/how-world-do-you-mock-name-attribute/) – Mr. Unnormalized Posterior Sep 08 '18 at 10:36
  • This doesn't seem to work when you mock two SQLAlchemy models in the same test. The first one works OK, but the second one will return a MagicMock regardless of what you define. – Juha Untinen Oct 23 '18 at 21:34
  • 2
    This explanation is way better than any python documentation. Thank you! – Sharanya Aug 09 '19 at 22:27
  • I am confused what does the parentheses do? @jonrsharpe – user13758558 Jun 07 '21 at 20:06
  • @user13758558 I don't know which parentheses you mean, but In Python they're either grouping (which doesn't appear in this post) or _calling_. – jonrsharpe Jun 07 '21 at 20:07
  • @jonrsharpe could you please help me with this? https://stackoverflow.com/questions/67878856/how-to-mock-function-calls-of-s3-boto3-python – user13758558 Jun 07 '21 at 21:31
  • 2
    and if you're asking the reason why it works ... that `mock_a()` return a singleton – Sławomir Lenart Sep 02 '21 at 15:09
  • You sir, are a gentleman and a scholar. THANK YOU! – Astockwell Dec 03 '21 at 20:04
  • 1
    I've set the `return_value` and the `mock` is returned anyways. These mocks are not easy to set up properly – WestCoastProjects Jan 10 '23 at 14:08
  • 1
    The first solution, `mock_a().method_a.return_value`, invokes `mock_a` in such a way that `mock_a.assert_called()` becomes true even if `mock_a` is never invoked by the code being tested. The second solution, `mock_a.return_value.method_a.return_value`, does not invoke `mock_a` in that way (`mock_a.assert_called()` remains false). For this reason, I prefer the second solution. – Rainbolt Mar 23 '23 at 16:26
2

In case of mocking an object I use this syntax:

@mock.patch.object(
    a.A,
    'method_a',
    lambda a: "Mocked A")
def test_method_b(self):
    b = a.B()
    b.method_b()

In this case the method_a is mocked by the lambda function.

ndrini
  • 81
  • 8
0

In my case, my return_value was this {"key1": "value1"}. So I needed to cast this with dict() before applying the patch.

Like this:

@fixture
def mock_something(mocker: MockFixture, request: SubRequest) -> MagicMock:
    method_to_mock = f"your_module.YourClass.some_method"
    return_value = dict(request.param)
    if isinstance(return_value, Exception):
        return mocker.patch(method_to_mock, side_effect=return_value)
    return mocker.patch(method_to_mock, return_value=return_value)
natielle
  • 380
  • 3
  • 14