I need to patch three methods (_send_reply
, _reset_watchdog
and _handle_set_watchdog
) with mock methods before testing a call to a fourth method (_handle_command
) in a unit test of mine.
From looking at the documentation for the mock package, there's a few ways I could go about it:
With patch.multiple
as decorator
@patch.multiple(MBG120Simulator, _send_reply=DEFAULT, _reset_watchdog=DEFAULT, _handle_set_watchdog=DEFAULT, autospec=True) def test_handle_command_too_short_v1(self, _send_reply, _reset_watchdog, _handle_set_watchdog): simulator = MBG120Simulator() simulator._handle_command('XA99') _send_reply.assert_called_once_with(simulator, 'X?') self.assertFalse(_reset_watchdog.called) self.assertFalse(_handle_set_watchdog.called) simulator.stop()
With patch.multiple
as context manager
def test_handle_command_too_short_v2(self): simulator = MBG120Simulator() with patch.multiple(simulator, _send_reply=DEFAULT, _reset_watchdog=DEFAULT, _handle_set_watchdog=DEFAULT, autospec=True) as mocks: simulator._handle_command('XA99') mocks['_send_reply'].assert_called_once_with('X?') self.assertFalse(mocks['_reset_watchdog'].called) self.assertFalse(mocks['_handle_set_watchdog'].called) simulator.stop()
With multiple patch.object
decoratorations
@patch.object(MBG120Simulator, '_send_reply', autospec=True) @patch.object(MBG120Simulator, '_reset_watchdog', autospec=True) @patch.object(MBG120Simulator, '_handle_set_watchdog', autospec=True) def test_handle_command_too_short_v3(self, _handle_set_watchdog_mock, _reset_watchdog_mock, _send_reply_mock): simulator = MBG120Simulator() simulator._handle_command('XA99') _send_reply_mock.assert_called_once_with(simulator, 'X?') self.assertFalse(_reset_watchdog_mock.called) self.assertFalse(_handle_set_watchdog_mock.called) simulator.stop()
Manually replacing methods using create_autospec
def test_handle_command_too_short_v4(self): simulator = MBG120Simulator() # Mock some methods. simulator._send_reply = create_autospec(simulator._send_reply) simulator._reset_watchdog = create_autospec(simulator._reset_watchdog) simulator._handle_set_watchdog = create_autospec(simulator._handle_set_watchdog) # Exercise. simulator._handle_command('XA99') # Check. simulator._send_reply.assert_called_once_with('X?') self.assertFalse(simulator._reset_watchdog.called) self.assertFalse(simulator._handle_set_watchdog.called)
Personally I think the last one is clearest to read, and will not result in horribly long lines if the number of mocked methods grow. It also avoids having to pass in simulator
as the first (self
) argument to assert_called_once_with
.
But I don't find any of them particularly nice. Especially the multiple patch.object
approach, which requires careful matching of the parameter order to the nested decorations.
Is there some approach I've missed, or a way to make this more readable? What do you do when you need to patch multiple methods on the instance/class under test?
patch() unittest. mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.
Mock vs. So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.
Patching vs Mocking: Patching a function is adjusting it's functionality. In the context of unit testing we patch a dependency away; so we replace the dependency. Mocking is imitating. Usually we patch a function to use a mock we control instead of a dependency we don't control.
No you didn't have missed anything really different from what you proposed.
About readability my taste is for decorator way because it remove the mocking stuff from test body... but it is just taste.
You are right: if you patch the static instance of the method by autospec=True
you must use self in assert_called_*
family check methods. But your case is just a small class because you know exactly what object you need to patch and you don't really need other context for your patch than test method.
You need just patch your object use it for all your test: often in tests you cannot have the instance to patch before doing your call and in these cases create_autospec
cannot be used: you can just patch the static instance of the methods instead.
If you are bothered by passing the instance to assert_called_*
methods consider to use ANY
to break the dependency. Finally I wrote hundreds of test like that and I never had a problem about the arguments order.
My standard approach at your test is
from unittest.mock import patch @patch('mbgmodule.MBG120Simulator._send_reply', autospec=True) @patch('mbgmodule.MBG120Simulator._reset_watchdog', autospec=True) @patch('mbgmodule.MBG120Simulator._handle_set_watchdog', autospec=True) def test_handle_command_too_short(self,mock_handle_set_watchdog, mock_reset_watchdog, mock_send_reply): simulator = MBG120Simulator() simulator._handle_command('XA99') # You can use ANY instead simulator if you don't know it mock_send_reply.assert_called_once_with(simulator, 'X?') self.assertFalse(mock_reset_watchdog.called) self.assertFalse(mock_handle_set_watchdog_mock.called) simulator.stop()
mock_
prefixpatch
call and absolute path: it is clear and neat what you are doingFinally: maybe create simulator
and stop it are setUp()
and tearDown()
responsibility and tests should take in account just to patch some methods and do the checks.
I hope that answer is useful but the question don't have a unique valid answer because readability is not an absolute concept and depends from the reader. Moreover even the title speaking about general case, question examples are about the specific class of problem where you should patch methods of the object to test.
[EDIT]
I though a while about this question and I found what bother me: you are trying to test and sense on private methods. When this happen the first thing that you should ask is why? There are a lot chances that the answer is because these methods should be public methods of private collaborators (that not my words).
In that new scenario you should sense on private collaborators and you cannot change just your object. What you need to do is to patch the static instance of some other classes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With