Successfully reported this slideshow.

Mock it right! A beginner’s guide to world of tests and mocks, Maciej Polańczyk

0

Share

Loading in …3
×
1 of 39
1 of 39

More Related Content

More from Pôle Systematic Paris-Region

Related Books

Free with a 30 day trial from Scribd

See all

Mock it right! A beginner’s guide to world of tests and mocks, Maciej Polańczyk

  1. 1. Mock it right! A beginner’s guide to world of tests and mocks. Maciej Polańczyk maciej.polanczyk@stxnext.pl Mock, Patch
  2. 2. Schedule - Class to be tested - Basic test - Mock - Not the best mock - The best mock - spec_set - Good mock - spec - Mock requests.post - Patch - Patch.object - Patch & import - Patch - raise Exception - Patch - different return_value - Patch - method instead of return_value - Patch - nesting patches - Patch - TestCase - Patch - in setUp - Patch - environment variables https://docs.python.org/3/library/unittest.mock.html
  3. 3. Class to be tested import requests class MessageSender: _max_attempts = 3 def send(self, message): data = { 'from': message.sender, 'to': message.receiver, 'subject': message.subject, } for attempt in range(self._max_attempts): response = requests.post(url='http://example.com/email', data=data) if response.status_code == requests.codes.created: return True return False
  4. 4. Basic test from message_sender import MessageSender class MessageSenderTests(unittest.TestCase): def test_should_send_message(): # GIVEN message_sender = MessageSender() message_to_be_sent = self._get_mocked_message() # WHEN result = message_sender.send(message_to_be_sent) # THEN self.assertTrue(result) # assert correct arguments were passed to request.post
  5. 5. Mock Use mock when you can pass it
  6. 6. Mock - creation from unittest.mock import Mock # mock and attributes mock = Mock() print(mock.not_existing_field) <Mock name='mock.not_existing_field' id='4327526528'> from unittest.mock import Mock # mock and methods mock = Mock() print(mock.not_existing_method()) <Mock name='mock.not_existing_method()' id='4372615856'>
  7. 7. Mock - creation from unittest.mock import Mock # mock and attributes mock = Mock() mock.existing_field = 5 print(mock.existing_field) 5 from unittest.mock import Mock # mock and methods mock = Mock() mock.existing_method.return_value = 5 print(mock.existing_method()) 5
  8. 8. Mock - creation from unittest.mock import Mock # mock and attributes mock = Mock(existing_field=5) print(mock.existing_field) 5 from unittest.mock import Mock # mock and methods mock = Mock(existing_method=Mock(return_value=5)) print(mock.existing_method()) 5
  9. 9. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock.existing_method() # in test method: mock.existing_method.assert_called_once_with()
  10. 10. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock.existing_method(1) # in test method: mock.existing_method.assert_called_once_with(1)
  11. 11. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock.existing_method(1) mock.existing_method(2) # in test method: mock.existing_method.assert_any_call(1)
  12. 12. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock(3) mock.existing_method(1) mock.existing_method(2) print(mock.call_args_list) output from print: [call(3)]
  13. 13. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock(3) mock.existing_method(1) mock.existing_method(2) print(mock.method_calls) output from print: [call.existing_method(1), call.existing_method(2)]
  14. 14. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock(3) mock.existing_method(1) mock.existing_method(2) print(mock.mock_calls) output from print: [call(3), call.existing_method(1), call.existing_method(2)]
  15. 15. Mock - assertions from unittest.mock import Mock, call # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock(3) mock.existing_method(1) mock.existing_method(2) # in test method: self.assertEqual(mock.mock_calls, [call(3), call.existing_method(1), call.existing_method(2)]) self.assertEqual(mock.existing_method.mock_calls, [call(1), call(2)])
  16. 16. Not the best mock from unittest.mock import Mock @staticmethod def _get_mocked_message(): message_mock = Mock( receiver='maciej.polanczyk@stxnext.pl', sender='maciej.polanczyk@test.com', subject='testing sending message' ) return message_mock
  17. 17. The best mock - spec_set from unittest.mock import Mock from message import Message @staticmethod def _get_mocked_message(): message_mock = Mock( spec_set=Message, receiver='maciej.polanczyk@stxnext.pl', sender='maciej.polanczyk@test.com', subject='testing sending message' ) return message_mock
  18. 18. Message class class Message: receiver = None sender = None subject = None
  19. 19. Message class def Message: def __init__(self, receiver, sender, subject): self.receiver = receiver self.sender = sender self.subject = subject
  20. 20. Good mock - spec import requests from unittest.mock import Mock @staticmethod def _get_success_response(): response_mock = Mock( spec=requests.Response, status_code=requests.codes.created ) return response_mock
  21. 21. Good mock - spec import requests from unittest.mock import Mock @staticmethod def _get_failure_response(): response_mock = Mock( spec=requests.Response, status_code=requests.codes.bad ) return response_mock
  22. 22. Basic test from message_sender import MessageSender class MessageSenderTests(unittest.TestCase): def test_should_send_message(): # GIVEN message_sender = MessageSender() message_to_be_sent = self._get_mocked_message() # WHEN result = message_sender.send(message_to_be_sent) # THEN self.assertTrue(result) # assert correct arguments were passed to request.post
  23. 23. Mock request.post import requests class MessageSender: _max_attempts = 3 def send(self, message): data = { 'from': message.sender, 'to': message.receiver, 'subject': message.subject, } for attempt in range(self._max_attempts): response = requests.post(url='http://example.com/email', data=data) if response.status_code == requests.codes.created: return True return False
  24. 24. Mock request.post class MessageSender: _max_attempts = 3 def __init__(): self._sender = ... def send(self, message): ... response = self._sender.post( url='http://example.com/email', data=data, ) ... class MessageSenderTests(unittest.TestCase): def test_should_send_message(self): # GIVEN message_sender = MessageSender() message_sender._sender = self._get_mocked_post_method() ... @staticmethod def _get_mocked_post_method(): post_method_mock = Mock( # spec_set=SenderClass return_value=MessageSenderTests._get_success_response() ) return post_method_mock
  25. 25. Patch Use patch when you can not pass mock
  26. 26. Patch from unittest.mock import patch from message_sender import MessageSender @patch('requests.post', autospec=True) def test_should_send_message(self, post_mock): # GIVEN post_mock.return_value = self._get_success_response() ... # THEN self.assertTrue(result) post_mock.assert_called_once_with( data={ 'from': 'maciej.polanczyk@test.com', ... }, url='http://example.com/email' )
  27. 27. Patch.object import requests from unittest.mock import patch from message_sender import MessageSender @patch.object(requests, 'post', autospec=True) def test_should_send_message(self, post_mock): # GIVEN post_mock.return_value = self._get_success_response() ... # THEN self.assertTrue(result) post_mock.assert_called_once_with( data={ ... }, url='http://example.com/email' )
  28. 28. Patch & imports from requests import post ... def send(self, message): ... response = post( url='http://example.com/email', data=data, ) ... from unittest.mock import patch import message_sender from message_sender import MessageSender @patch.object(message_sender, 'post', autospec=True) def test_should_send_message(self, post_mock): ... import requests ... def send(self, message): ... response = requests.post( url='http://example.com/email', data=data, ) … import requests from unittest.mock import patch from message_sender import MessageSender @patch.object(requests, 'post', autospec=True) def test_should_send_message(self, post_mock): ...
  29. 29. Patch - raise Exception import requests class MessageSender: _max_attempts = 3 def send(self, message): data = { 'from': message.sender, 'to': message.receiver, 'subject': message.subject, } for attempt in range(self._max_attempts): response = requests.post(url='http://example.com/email', data=data) if response.status_code == requests.codes.created: return True return False
  30. 30. Patch - raise Exception @patch.object(requests, 'post') def test_should_not_catch_exception(self, post_mock): # GIVEN post_mock.side_effect = RequestException( 'Expected exception from unit tests' ) message_sender = MessageSender() message_to_be_sent = self._get_example_message() # WHEN & THEN with self.assertRaisesRegex(RequestException, 'Expected exception'): message_sender.send(message_to_be_sent) post_mock.assert_called_once_with( data={ ... }, url='http://example.com/email' )
  31. 31. Patch - different return_value import requests class MessageSender: _max_attempts = 3 def send(self, message): data = { 'from': message.sender, 'to': message.receiver, 'subject': message.subject, } for attempt in range(self._max_attempts): response = requests.post(url='http://example.com/email', data=data) if response.status_code == requests.codes.created: return True return False
  32. 32. Patch - different return_value @patch.object(requests, 'post', autospec=True) def test_should_retry_sending_when_incorrect_status_received(self, post_mock): # GIVEN post_mock.side_effect = [self._get_failure_response(), self._get_failure_response(), self._get_failure_response(),] message_to_be_sent = self._get_example_message() message_sender = MessageSender() # WHEN result = message_sender.send(message_to_be_sent) # THEN self.assertFalse(result) expected_calls = [self._get_expected_call(), self._get_expected_call(), self._get_expected_call(),] self.assertEqual(post_mock.call_args_list, expected_calls) def _get_expected_call(self): return call( data = { 'from': 'maciej.polanczyk@test.com', 'subject': 'testing sending message', 'to': 'maciej.polanczyk@stxnext.pl' }, url = 'http://example.com/email' )
  33. 33. Patch - method instead of return_value @patch.object(requests, 'post', autospec=True) def test_should_send_message_tooo(self, post_mock): # GIVEN def implementation_from_unit_test(*args, **kwargs): return self._get_success_response() post_mock.side_effect = implementation_from_unit_test ...
  34. 34. Patch - nesting patches @patch.object(requests, 'post', autospec=True) @patch.object(requests, 'get', autospec=True) @patch.object(requests, 'put', autospec=True) def test_should_send_message_tooo(self, put_mock, get_mock, post_mock): # GIVEN ... # WHEN ... # THEN ...
  35. 35. Patch - TestCase class MessageSenderTests(unittest.TestCase): @patch.object(requests, 'post', autospec=True) def test_should_send_message(self, post_mock): pass @patch.object(requests, 'post', autospec=True) def test_should_not_catch_exception(self, post_mock): pass @patch.object(requests, 'post', autospec=True) class MessageSenderTests(unittest.TestCase): def test_should_send_message(self, post_mock): pass def test_should_not_catch_exception(self, post_mock): pass
  36. 36. Patch - in setUp class MessageSenderTests(unittest.TestCase): def setUp(self): super().setUp() patcher = patch('requests.post') self.addCleanup(patcher.stop) self._post_mock = patcher.start() def test_should_send_message(self): # GIVEN self._post_mock.return_value = self._get_success_response() # WHEN ... # THEN ...
  37. 37. Patch - environment variables class MessageSenderTests(unittest.TestCase): @patch.dict('os.environ', {'not_existing_key': 'some_value'}, clear=True) def test_should_override_environment_variables(self): self.assertEqual(os.environ, {'not_existing_key': 'some_value'})
  38. 38. Summary Use mock when you can pass it Use patch when you can not pass mock
  39. 39. Thank you! Questions?

×