Preferred way to fix multiple methods in Python unit test

I need to fix three methods ( _send_reply , _reset_watchdog and _handle_set_watchdog ) using false methods before testing the fourth method call ( _handle_command ) in the unit test.

There are several ways to look at the documentation for the mock package:

With patch.multiple as a 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 a 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 decorators

 @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() 

Manual method replacement with 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 that the latter is the most readable for reading, and will not lead to terribly long lines if the number of mocking methods grows. It also avoids passing in simulator as the first ( self ) argument in assert_called_once_with .

But I do not find them particularly pleasant. The patch.object approach is especially repeated, which requires careful coordination of the order of parameters with nested decorations.

Is there any approach I skipped or a way to make this more readable? What do you do when you need to fix several methods in a test instance / class?

+7
python unit-testing mocking
source share
1 answer

No, you have not missed anything other than what you suggested.

On readability, my taste is for the decorator because it removes mocking things from the testing body ... but it just tastes.

You are right: if you fix a static instance of a method to autospec=True , you should use the self in assert_called_* family validation methods. But your case is just a small class, because you know exactly what object you need to fix, and you do not need a different context for your patch than the testing method.

You just need to fix your object, using it for the whole test: often in tests you cannot have an instance to fix before making your call, and in these cases create_autospec cannot be used: you can just fix the static instance instead.

If it bothers you to pass an instance to the assert_called_* method, consider using ANY to break the dependency. Finally, I wrote hundreds of such tests, and I never had a problem with the order of the arguments.

My standard approach when testing

 @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() 
  • Correction from the verification method code
  • Each layout begins with the prefix mock_
  • I prefer to use a simple patch call and an absolute path: it’s clear and neat what you do

Finally: perhaps create a simulator and stop it with setUp() and tearDown() responsibility, and tests should be taken into account only to fix some methods and perform checks.

I hope the answer is useful, but the question does not have a unique valid answer, because readability is not an absolute concept and depends on the reader. Moreover, even the heading talking about the general case contains examples of questions about a specific class of problems, where you must correct the methods of the object under test.

[EDIT]

I, although for a while about this question, and I found that bothers me: you are trying to test and feel private methods. When this happens, the first thing you should ask is why? There are many chances that the answer is that these methods should be publicly available methods of private co-authors ( which is not my words ).

In this new scenario, you should feel yourself in private collaborators, and you cannot change only your object. What you need to do is fix the static instance of some other classes.

+4
source share

All Articles