简体   繁体   中英

Python3 mock: assert_has_calls for production-code methods?

I've got this production class:

class MyClass:
    def __init__(self):
        self.value = None

    def set_value(self, value):
        self.value = value

    def foo(self):
        # work with self.value here
        # raise RuntimeError("error!")
        return "a"

Which is being used from another place, like this:

class Caller:
    def bar(self, smth):
        obj = MyClass()
        obj.set_value(smth)
        # ...
        # try:
        obj.foo()
        # except MyError:
        #     pass

        obj.set_value("str2")
        # obj.foo()

and I got this:

class MyError(Exception):
    pass

In my test I want to make sure that Caller.bar calls obj.set_value, first with smth="a", then with smth="b", but I want it to really set the value (ie call the real set_value method). Is there any way for me to tell the mock to use the actual method, so I can later on read what it was called with?

PS I know that I can just change "foo" to require the parameter "smth" so I could get rid of "set_value", but I want to know if there is another option than this.


Okay, so I have tried this in my test:

def test_caller(self):
     with patch('fullpath.to.MyClass', autospec=MyClass) as mock:
         mock.foo.side_effect = [MyError("msg"), "text"]

         caller = Caller()
         caller.bar("str1")

         calls = [call("str1"), call("str2")]
         mock.set_value.assert_has_calls(calls)

But I see that the mock was not successful since the real "foo" is called when I wanted it to first raise MyError, then return "text".

Also, the assertion fails:

AssertionError: Calls not found.
Expected: [call('str1'), call('str2')]
Actual: []

The problem here is that you have mocked out your Class , and are not properly using the instance of your class. This is why things are not behaving as expected.

So, lets take a look at what is going on.

Right here:

with patch('fullpath.to.MyClass', autospec=MyClass) as mock:

So, what you are doing right here is mocking out your class MyClass only. So, when you are doing this:

mock.set_value.assert_has_calls(calls)

And inspect what is going on when you execute your unittest, your mock calls will actually contain this:

[call().set_value('str1'), call().foo(), call().set_value('str2')]

Pay attention to call as it is written as call() . call is with reference to your mock here. So, with that in mind, you need to use the called (aka return_value within context of the mocking world) mock to properly reference your mock object that you are trying to test with. The quick way to fix this is simply use mock() . So you would just need to change to this:

mock().set_value.assert_has_calls(calls)

However, to be more explicit on what you are doing, you can state that you are actually using the result of calling mock . Furthermore, it would actually be good to note to use a more explicit name, other than mock . Try MyClassMock , which in turn you name your instance my_class_mock_obj :

my_class_mock_obj = MyClassMock.return_value

So in your unit test it is more explicit that you are using a mocked object of your class. Also, it is always best to set up all your mocking before you make your method call, and for your foo.side_effect ensure that you are also using the instance mock object. Based on your recent update with your exception handling, keep your try/except without comments. Putting this all together, you have:

def test_caller(self):
    with patch('tests.test_dummy.MyClass', autospec=MyClass) as MyClassMock:
        my_class_mock_obj = MyClassMock.return_value
        my_class_mock_obj.foo.side_effect = [MyError("msg"), "text"]

        caller = Caller()
        caller.bar("str1")

        calls = [call("str1"), call("str2")]

        my_class_mock_obj.set_value.assert_has_calls(calls)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM