Python mocking intro

HansJones1 78 views 33 slides Feb 20, 2023
Slide 1
Slide 1 of 33
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33

About This Presentation

Python Mocking in Software Testing


Slide Content

Python mocking intro IoC, DIP, DI and IoC containers

Mock Objects In Object-Oriented programming mock objects are defined as simulated objects They mimic the behavior of real objects in controlled ways The whole point of unit testing is isolating certain functionality (unit) and test just that. We are trying to remove all other dependencies Sometimes this can be a database, networking or a file system, so using mock object we don’t step into the world of integration tests Other times dependency can be just some other class or function A mock object should be used in the situations when The real object is slow The real object rarely occurs and is difficult to produce artificially The real object produces non-deterministic results The real object does not yet exist (which often is the case in TDD)

Some external dependencies External resources are a common source of non-unit test dependencies Registry and environment variables Network connections and databases Files and directories Current date and time Hardware devices And so on… External dependencies can often be replaced by test doubles or pre-evaluated objects Test Double (think stunt double) is a generic term for any case where you replace a production object for testing purposes Pre-evaluated objects can be already fetched/computed records in memory arrays

Some various test doubles Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an In-Memory Test Database is a good example) Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it has sent Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting Source: https://martinfowler.com/bliki/TestDouble.html

Test doubles and unittest.mock Mocking and related techniques may be easier in Python than in many other languages Dependencies can be substituted without changing class design The unittest.mock library provides a core Mock class removing the need to create a host of stubs throughout your test suite After performing an action, you can make assertions about which attributes and methods were used and arguments they were called with You can also specify return values and set needed attributes in the normal way Additionally, mock provides a patch() decorator that handles patching module and class level attributes within the scope of a test for creating unique objects Mock is relatively easy to use and is designed for use with unittest https://docs.python.org/3/library/unittest.mock.html

unittest.mock Quick Guide – return_value and side_effect from unittest.mock import Mock # import the Mock class from unittest.mock import MagicMock # import the MagicMock class class ProductionClass(): def __init__(self): pass # Mock and MagicMock objects create all attributes and methods as you access them and store details # of how they have been used. You can configure them, to specify return values or limit what # attributes are available, and then make assertions about how they have been used thing = ProductionClass() thing.method = MagicMock(return_value = 3) # calling method() will return 3 print(thing.method()) # print the return_value thing.method(3, 4, 5, key = 'value') thing.method.assert_called_with(3, 4, 5, key ='value') # AssertionError if different than expected call print(dir(Mock())) # show all the default mock methods / attributes # side_effect allows you to perform side effects, including raising an exception when a mock is called my_mock = Mock(side_effect = KeyError('foo')) # create a new mock object with KeyError exception # my_mock() # mock -> side_effect = KeyError exception values = {'a': 1, 'b': 2, 'c': 3} def side_effect_func(arg): return values[arg] my_mock.side_effect = side_effect_func # mock -> side_effect_func print(my_mock('a'), my_mock('b'), my_mock('c')) # print values side_effect_func(arg) my_mock.side_effect = [5, 4, 3, 2, 1] # change side_effect again for x in range(0, 5): # side_effect now returns array one by one print(my_mock()) quick_guide_mock1.py # output thing.method(): 3 dir(Mock()): ['assert_any_call', 'assert_called', 'assert_called_once', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'assert_not_called', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect'] dict: 1 2 3 range: 5 4 3 2 1

The Mock / MagicMock classes and patch Mock is a flexible mock object intended to replace the use of stubs and other test doubles throughout your code Mocks are callable and create attributes as new mocks when you access them. Accessing the same attribute will always return the same mock Mocks record how you use them, allowing you to make assertions about what your code has done to them MagicMock is a subclass of Mock with all the magic methods (__init__, __str__, __new__, __main__, __name__, ...) pre-created and ready to use https://docs.python.org/3/library/unittest.mock.html#magicmock-and-magic-method-support https://rszalski.github.io/magicmethods/ You can use MagicMock without having to configure the magic methods yourself The constructor parameters have the same meaning as for Mock The patch () decorators makes it easy to temporarily replace classes in a particular module with a Mock object. By default patch() will create a MagicMock for you # patched classes are input @patch('my_mod.ClsName') def testFunc(MockCls): # MockCls == my_mod.ClsName assert MockCls is my_mod.ClsName # call patched class or MockCls.Mthd() my_mod.ClsName() MockCls.Mthd = Mock(return_value = ...

Create a Mock object Create a new Mock object. Mock takes several optional arguments that specify the behaviour of the Mock object: spec : This can be either a list of strings or an existing object (a class or instance) that acts as the specification for the mock object. If you pass in an object then a list of strings is formed by calling dir on the object (excluding unsupported magic attributes and methods). Accessing any attribute not in this list will raise an AttributeError. If spec is an object (rather than a list of strings) then __class__ returns the class of the spec object. This allows mocks to pass isinstance() tests. spec_set : A stricter variant of spec. If used, attempting to set or get an attribute on the mock that isn’t on the object passed as spec_set will raise an AttributeError. side_effect : A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT, the return value of this function is used as the return value. Alternatively side_effect can be an exception class or instance. In this case the exception will be raised when the mock is called. If side_effect is an iterable then each call to the mock will return the next value from the iterable. A side_effect can be cleared by setting it to None. return_value : The value returned when the mock is called. By default this is a new Mock (created on first access). See the return_value attribute. unsafe : By default if any attribute starts with assert or assret will raise an AttributeError. Passing unsafe=True will allow access to these attributes. wraps : Item for the mock object to wrap. If wraps is not None then calling the Mock will pass the call through to the wrapped object (returning the real result). Attribute access on the mock will return a Mock object that wraps the corresponding attribute of the wrapped object (so attempting to access an attribute that doesn’t exist will raise an AttributeError). If the mock has an explicit return_value set then calls are not passed to the wrapped object and the return_value is returned instead. name : If the mock has a name then it will be used in the repr of the mock. This can be useful for debugging. The name is propagated to child mocks. class unittest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs) https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock

Mock class attributes 1 Mocks can also be called with arbitrary keyword arguments. These will be used to set attributes on the mock after it is created: assert_called(*args, **kwargs) Assert that the mock was called at least once. assert_called_once(*args, **kwargs) Assert that the mock was called exactly once. assert_called_with(*args, **kwargs) This method is a convenient way of asserting that calls are made in a particular way. assert_called_once_with(*args, **kwargs) Assert that the mock was called exactly once and that that call was with the specified arguments. assert_any_call(*args, **kwargs) Assert that the mock has been called with the specified arguments. assert_has_calls(calls, any_order=False) Assert that the mock has been called with the specified calls. The mock_calls list is checked for the calls.. assert_not_called() Assert that the mock was never called. reset_mock(*, return_value=False, side_effect=False) The reset_mock method resets all the call attributes on a mock object. mock_add_spec(spec, spec_set=False) Add a spec. to a mock attach_mock(mock, attribute) Attach a mock as an attribute of this one, replacing its name and parent. configure_mock(**kwargs) Set attributes on the mock through keyword arguments. # examples mock_obj.assert_not_called() mock_obj.assert_called_with(10)

Mock class attributes 2 __dir__() Mock objects limit the results of dir(some_mock) to useful results _get_child_mock(**kw) Create the child mocks for attributes and return value called A boolean representing whether or not the mock object has been called call_count An integer telling you how many times the mock object has been called return_value Set this to configure the value returned by calling the mock side_effect This can either be a function to be called when the mock is called, an iterable or an exception (class or instance) to be raised call_args This is either None (if the mock hasn’t been called), or the arguments that the mock was last called with call_args_list This is a list of all the calls made to the mock object in sequence method_calls As well as tracking calls to themselves, mocks also track calls to methods and attributes, and their methods and attributes mock_calls mock_calls records all calls to the mock object, its methods, magic methods and return value mocks __class__ Normally the __class__ attribute of an object will return its type. For a mock object with a spec, __class__ returns the spec class instead # examples mock_obj.return_value = 10 mock_obj.side_effect = [1, 2, 3]

Using Mock objects Calling Mock objects are callable. The call will return the value set as the return_value attribute. The default return value is a new Mock object; it is created the first time the return value is accessed (either explicitly or by calling the Mock) - but it is stored and the same one returned each time https://docs.python.org/3/library/unittest.mock.html#calling Deleting attributes Mock objects create attributes on demand. This allows them to pretend to be objects of any type. You “block” attributes by deleting them. Once deleted, accessing an attribute will raise an AttributeError https://docs.python.org/3/library/unittest.mock.html#deleting-attributes Mock names and the name attribute Since “name” is an argument to the Mock constructor, if you want your mock object to have a “name” attribute you can’t just pass it in at creation time. There are two alternatives. One option is to use configure_mock() https://docs.python.org/3/library/unittest.mock.html#mock-names-and-the-name-attribute Attaching Mocks as Attributes When you attach a mock as an attribute of another mock (or as the return value) it becomes a “child” of that mock. Calls to the child are recorded in the method_calls and mock_calls attributes of the parent. This is useful for configuring child mocks and then attaching them to the parent, or for attaching mocks to a parent that records all calls to the children and allows you to make assertions about the order of calls between mocks https://docs.python.org/3/library/unittest.mock.html#attaching-mocks-as-attributes del mock_obj.my_attr

Basic concepts unittest.mock – return_value from unittest import mock # import the library def print_answer(): print("42") def print_number(num): print(f"Number: {num}") m1 = mock.Mock() # The main object that the library provides is Mock and you can instantiate it without any argument print(dir(m1)) # show all the default mock methods / attributes print(m1.some_attribute) # read a non-existent attribute # Mock objects are callables, which means that they may act both as attributes and as methods. If you try to call # the mock it just returns you another mock with a name that includes parentheses to signal its callable nature print(m1.some_attribute()) # The simplest thing a mock can do for you is to return a given value every time you call it. # This is configured setting the return_value attribute of a mock object m1.some_attribute.return_value = 42 print(m1.some_attribute()) # Now the object does not return a mock object any more, instead it just returns the static value stored in the return_value attribute. # Obviously you can also store a callable such as a function or an object,and the method will return it, but it will not run it. m1.some_attribute.return_value = print_answer print(m1.some_attribute()) # As you can see calling some_attribute() just returns the value stored in return_value, that is the function itself. # To return values that come from a function we have to use a slightly more complex attribute of mock objects called side_effect. gentle_intro_to_mock1.py

Basic concepts unittest.mock – side_effect 1 # The side_effect parameter of mock objects is a very powerful tool. It accepts 3 different flavours of objects, callables, # iterables, and exceptions, and changes its behaviour accordingly. If you pass an exception the mock will raise it m1.some_attribute.side_effect = ValueError('A custom value error') try: m1.some_attribute() except BaseException as ex: print(f"ValueError: {ex}") # If you pass an iterable, such as for example a generator, or a plain list, tuple, or similar objects, the mock will # yield the values of that iterable, i.e. return every value contained in the iterable on subsequent calls of the mock. m1.some_attribute.side_effect = range(3) print(m1.some_attribute()) print(m1.some_attribute()) print(m1.some_attribute()) # print(m1.some_attribute()) try: print(m1.some_attribute()) except BaseException as ex: print(f"StopIteration().value: {StopIteration().value}") # As promised, the mock just returns every object found in the iterable (in this case a range object) once at a time # until the generator is exhausted. According to the iterator protocol once every item has been returned # the object raises the StopIteration exception, which means that you can correctly use it in a loop. gentle_intro_to_mock1.py

Basic concepts unittest.mock – side_effect 2 # The last and perhaps most used case is that of passing a callable to side_effect, which shamelessly executes it with # its own same parameters. This is very powerful, especially if you stop thinking about "functions" and start considering # "callables". Indeed, side_effect also accepts a class and calls it, that is it can instantiate objects. Let us consider # a simple example with a function without arguments m1.some_attribute.side_effect = print_answer m1.some_attribute() # A slightly more complex example: a function with arguments m1.some_attribute.side_effect = print_number m1.some_attribute.side_effect(5) # And finally an example with a class m1.some_attribute.side_effect = Number n1 = m1.some_attribute.side_effect(26) n1.print_value() # All the default mock methods / attributes # ['assert_any_call', 'assert_called', 'assert_called_once', 'assert_called_once_with', 'assert_called_with', # 'assert_has_calls', 'assert_not_called', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', # 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect'] # From: http://www.thedigitalcatonline.com/blog/2016/03/06/python-mocks-a-gentle-introduction-part-1/ class Number(object): def __init__(self, value): self._value = value def print_value(self): print("Value:", self._value) def print_answer(): print("42") def print_number(num): print(f"Number: {num}") gentle_intro_to_mock1.py

Basic concepts unittest.mock - assert_called_with According to Sandi Metz (programmer and author) we need to test only 3 types of messages (calls) between objects Incoming queries (assertion on result) Incoming commands (assertion on direct public side effects) Outgoing commands (expectation on call and arguments) https://www.sandimetz.com/ What we usually are interested in when dealing with an external object is to know that a given method has been called on it Python mocks provide the assert_called_with() method to check if a method has been called on it from unittest import mock import unittest import myobj class TestMocking(unittest.TestCase): def test_instantiation(self): external_obj = mock.Mock() myobj.MyObj(external_obj) external_obj.connect.assert_called_with() def test_setup(self): external_obj = mock.Mock() obj = myobj.MyObj(external_obj) obj.setup() external_obj.setup.assert_called_with( cache=True, max_connections=256) # AssertionError if __name__ == '__main__': unittest.main() class MyObj(): def __init__(self, repo): self._repo = repo repo.connect() def setup(self): self._repo.setup(cache=True) test_gentle_intro_to_mock1.py

unittest.mock Mock class We check that the Hello.bar() method is called correct with ’HELLO’ camelCase setUp() and tearDown() can perform initialization and cleanup of the test fixture since they are called before and after the test import unittest from unittest.mock import Mock class HelloTestTest(unittest.TestCase): def setUp(self): self.hello = Hello() def tearDown(self): pass def test_foo(self): msg = 'hello' expected_bar_arg = 'HELLO' self.hello.bar = Mock() # we mock the bar() method self.hello.foo(msg) # we check that bar was called with 'HELLO' self.hello.bar.assert_called_once_with(expected_bar_arg) if __name__ == '__main__': unittest.main(argv=['first-arg-is-ignored'], exit=False) class Hello(object): def foo(self, msg): MSG = msg.upper() self.bar(MSG) # the mocked bar() method # def bar(self, MSG): # print(MSG) test_hellomock.py

unittest.mock MagicMock class MagicMock is a subclass of Mock with default implementations of most of the magic methods You can use MagicMock without having to configure the magic methods yourself import unittest from unittest.mock import MagicMock from employee import Employee class TestMocking(unittest.TestCase): def test_mock1(self): thing = Employee('Hans', 'Jones', 50) # set return value thing.method = MagicMock(return_value = 3) # call the method ret = thing.method(3, 4, 5, key='value') print(f'ret: {ret}') # check if we called correct thing.method.assert_called_with(3, 4, 5, key='value') if __name__ == '__main__': unittest.main(argv=['first-arg-is-ignored'], exit=False) test_mock.py class Employee: """A sample Employee class""" raise_amt = 1.05 def __init__(self, first, last, pay): self.first = first self.last = last self.pay = pay

The patchers The patch decorators are used for patching objects only within the scope of the function they decorate. They automatically handle the unpatching for you, even if exceptions are raised. All of these functions can also be used in with statements or as class decorators. patch() acts as a function decorator, class decorator or a context manager. Inside the body of the function or with statement, the target is patched with a new object. When the function/with statement exits the patch is undone. If new is omitted, then the target is replaced with a MagicMock. If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function. If patch() is used as a context manager the created mock is returned by the context manager. target should be a string in the form 'package.module.ClassName'. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch() from. The target is imported when the decorated function is executed, not at decoration time. The spec and spec_set keyword arguments are passed to the MagicMock if patch is creating one for you. In addition you can pass spec=True or spec_set=True , which causes patch to pass in the object being mocked as the spec/spec_set object. new_callable allows you to specify a different class, or callable object, that will be called to create the new object. By default MagicMock is used. A more powerful form of spec is autospec . If you set autospec=True then the mock will be created with a spec from the object being replaced. All attributes of the mock will also have the spec of the corresponding attribute of the object being replaced. Methods and functions being mocked will have their arguments checked and will raise a TypeError if they are called with the wrong signature. For mocks replacing a class, their return value (the ‘instance’) will have the same spec as the class. See the create_autospec() function and Autospeccing. Instead of autospec=True you can pass autospec=some_object to use an arbitrary object as the spec instead of the one being replaced. unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch

patch.object, patch.dict and patch.multiple Patch the named member (attribute) on an object (target) with a mock object. patch.object() can be used as a decorator, class decorator or a context manager. Arguments new, spec, create, spec_set, autospec and new_callable have the same meaning as for patch(). Like patch(), patch.object() takes arbitrary keyword arguments for configuring the mock object it creates. When used as a class decorator patch.object() honours patch.TEST_PREFIX for choosing which methods to wrap. Patch a dictionary , or dictionary like object, and restore the dictionary to its original state after the test. in_dict can be a dictionary or a mapping like container. If it is a mapping then it must at least support getting, setting and deleting items plus iterating over keys. in_dict can also be a string specifying the name of the dictionary, which will then be fetched by importing it. values can be a dictionary of values to set in the dictionary. values can also be an iterable of (key, value) pairs. Perform multiple patches in a single call. It takes the object to be patched (either as an object or a string to fetch the object by importing) and keyword arguments for the patches Use DEFAULT as the value if you want patch.multiple() to create mocks for you. In this case the created mocks are passed into a decorated function by keyword, and a dictionary is returned when patch.multiple() is used as a context manager patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch patch.dict(in_dict, values=(), clear=False, **kwargs) patch.multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

unittest.mock patch() function patch() is the main mocking mechanism for the unittest.mock library patch() works by (temporarily) changing the object that a name points to with another one The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined class TestMocking(unittest.TestCase): # mocking MyMockedClass def test_mock2(self): # Patching a class replaces the class with a MagicMock instance with patch('__main__.MyMockedClass') as MockClass: # return_value of the mock will be used instance = MockClass.return_value instance.method.return_value = 'foo' # assert MyMockedClass() is instance of MyMockedClass self.assertIsInstance(instance, type(MyMockedClass())) self.assertEqual(MyMockedClass().method(), 'foo' ) self.assertEqual(instance.method(), 'foo' ) if __name__ == '__main__': unittest.main(argv=['first-arg-is-ignored'], exit=False) import unittest from unittest.mock import patch class MyMockedClass: def method(self): pass test_mock.py

unittest.mock Quick Guide – patch() from unittest.mock import Mock # import the Mock class from unittest.mock import MagicMock # import the MagicMock class from unittest.mock import patch import myobj class ProductionClass(): def method(self, a, b, c): pass # The patch() decorator / context manager makes it easy to mock classes or objects in a module under test. # The object you specify will be replaced with a mock (or other object) during the test and restored when the test ends @patch('myobj.ClassName2') @patch('myobj.ClassName1') # bottom-up passing of values def test(MockClass1, MockClass2): # patched classes are input myobj.ClassName1() # call patched class myobj.ClassName2() assert MockClass1 is myobj.ClassName1 # patched class is genuine assert MockClass2 is myobj.ClassName2 assert MockClass1.called == True # patched class have been called assert MockClass2.called == True MockClass1.return_value = 'No AssertionError!' return MockClass1.return_value print(f'test(): {test()}') # printed values from test() comes from patched objects # The patch.object() with patch.object(ProductionClass, 'method', return_value=None) as mock_method: thing = ProductionClass() thing.method(1, 2, 3) print(mock_method.assert_called_once_with(1, 2, 3)) # patched class have been called once with (1, 2, 3) and retuns None quick_guide_mock2.py myobj.py # The patch.dict() foo = {'key': 'value'} original = foo.copy() with patch.dict(foo, {'newkey': 'newvalue'}, clear=True): assert foo == {'newkey': 'newvalue'} # foo has new values assert foo == original # output method_cs1 test(): No AssertionError! mock_method.assert_called_once_with(1, 2, 3): None class ClassName1(): def __init__(self): pass class ClassName2(): def __init__(self): pass

unittest.mock Quick Guide – autospec # Mock supports the mocking of Python magic methods. The easiest way of using magic methods is with the MagicMock class. It allows you to do things like: mock = MagicMock() mock.__str__.return_value = 'foobarbaz' # return str(self) print(str(mock)) # 'foobarbaz' print(mock.__str__.assert_called_with()) # return str(self) # Mock allows you to assign functions (or other Mock instances) to magic methods and they will be called appropriately. # The MagicMock class is just a Mock variant that has all of the magic methods pre-created for you (well, all the useful ones anyway). # The following is an example of using magic methods with the ordinary Mock class: mock = Mock() mock.__str__ = Mock(return_value='wheeeeee') print(str(mock)) #'wheeeeee' # note the () difference mock.return_value = 'whoooooo' print(str(mock())) #'whoooooo' # For ensuring that the mock objects in your tests have the same api as the objects they are replacing, you can use auto-speccing. # Auto-speccing can be done through the autospec argument to patch, or the create_autospec() function. Auto-speccing creates mock objects that have the # same attributes and methods as the objects they are replacing, and any functions and methods # (including constructors) have the same call signature as the real object. # This ensures that your mocks will fail in the same way as your production code if they are used incorrectly: from unittest.mock import create_autospec def function(a, b, c): pass mock_function = create_autospec(function, return_value='fishy') print(mock_function(1, 2, 3)) # 'fishy' print(mock_function.assert_called_once_with(1, 2, 3)) #mock_function('wrong arguments') # TypeError: missing a required argument: 'b' # create_autospec() can also be used on classes, where it copies the signature of the # __init__ method, and on callable objects where it copies the signature of the __call__ method. str(mock): foobarbaz mock.__str__.assert_called_with(): None str(mock): wheeeeee str(mock()): whoooooo mock_function(1, 2, 3): fishy mock_function.assert_called_once_with(1, 2, 3): None quick_guide_mock2.py myobj.py

patch() and new_callable patch() away sys.std - we replace an object (sys.stdout) with an io.StringIO instance with the @patch decorator patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) from io import StringIO import unittest from unittest.mock import patch class MyMockedClass: def foo_print(self): print( 'Something that is not going to be printed to sys.stdout' ) class TestMocking(unittest.TestCase): @patch('sys.stdout', new_callable=StringIO) def test_mock3(self, mock_stdout): thing = MyMockedClass() thing.foo_print() # assert mock_stdout.getvalue() == 'Something\n' # sys.stdout.getvalue() self.assertEqual(mock_stdout.getvalue(), 'Something that is not going to be printed to sys.stdout\n' ) if __name__ == '__main__': unittest.main(argv=['first-arg-is-ignored'], exit=False) test_mock.py

Basic concepts unittest.mock – patch 1 import unittest from fileinfo import FileInfo from unittest.mock import patch from logger import Logger class TestMocking(unittest.TestCase): @ classmethod def setUpClass(cls): cls.filename = 'somefile.ext' def test_init_filename(self): fi = FileInfo(self.filename) self.assertEqual(fi.filename, self.filename) def test_init_path(self): relative_path = f'../{self.filename}' fi = FileInfo(relative_path) self.assertEqual(fi.filename, self.filename) def test_get_info1(self): original_path = f'../{self.filename}' with patch('os.path.abspath') as abspath_mock: # patching os.path.abspath with patch() test_abspath = 'some/abs/path' abspath_mock.return_value = test_abspath # mocking os.path.abspat h fi = FileInfo(original_path) retval = fi.get_info() self.assertEqual(retval, (self.filename, original_path, test_abspath)) import os class FileInfo: def __init__(self, path): self.original_path = path self.filename = os.path.basename(path) def get_info(self): return self.filename, self.original_path, os.path.abspath(self.filename) def get_info_size(self): return self.filename, self.original_path, os.path.abspath(self.filename), os.path.getsize(self.filename) test_gentle_intro_to_mock2.py

Basic concepts unittest.mock – patch 2 @patch('os.path.abspath') # The patching decorator def test_get_info2(self, abspath_mock): original_path = f'../{self.filename}' test_abspath = 'some/abs/path' abspath_mock.return_value = test_abspath fi = FileInfo(original_path) self.assertEqual(fi.get_info(), (self.filename, original_path, test_abspath)) @patch('os.path.getsize') # Multiple patches @patch('os.path.abspath') # bottom-up passing of values def test_get_info3(self, abspath_mock, getsize_mock): original_path = f'../{self.filename}' test_abspath = 'some/abs/path' abspath_mock.return_value = test_abspath test_size = 1234 getsize_mock.return_value = test_size fi = FileInfo(original_path) self.assertEqual(fi.get_info_size(), (self.filename, original_path, test_abspath, test_size)) def test_get_info4(self): original_path = f'../{self.filename}' # We can write the above test using two with statements as well with patch('os.path.abspath') as abspath_mock: test_abspath = 'some/abs/path' abspath_mock.return_value = test_abspath with patch('os.path.getsize') as getsize_mock: test_size = 1234 getsize_mock.return_value = test_size fi = FileInfo(original_path) self.assertEqual(fi.get_info_size(), (self.filename, original_path, test_abspath, test_size)) test_gentle_intro_to_mock2.py

Basic concepts unittest.mock – patch 3 # Patching immutable objects == no change after creation def test_init_logger(self): lg = Logger() self.assertEqual(lg.messages, []) # @patch('datetime.datetime.now') # def test_log1(self, mock_now): # test_now = 123 # test_message = "A test message" # mock_now.return_value = test_now # lg = Logger() # lg.log(test_message) # self.assertEqual(lg.messages, [(test_now, test_message)]) @patch('logger.datetime.datetime') def test_log2(self, mock_datetime): test_now = 123 test_message = "A test message" mock_datetime.now.return_value = test_now lg = Logger() lg.log(test_message) self.assertEqual(lg.messages, [(test_now, test_message)]) if __name__ == '__main__': unittest.main(argv=['first-arg-is-ignored'], exit=False) import datetime class Logger: def __init__(self): self.messages = [] def log(self, message): self.messages.append((datetime.datetime.now(), message)) test_get_info1 (__main__.TestMocking) ... ok test_get_info2 (__main__.TestMocking) ... ok test_get_info3 (__main__.TestMocking) ... ok test_get_info4 (__main__.TestMocking) ... ok test_init_filename (__main__.TestMocking) ... ok test_init_logger (__main__.TestMocking) ... ok test_init_path (__main__.TestMocking) ... ok test_log2 (__main__.TestMocking) ... ok test_gentle_intro_to_mock2.py

unittest.mock patch() a web request To get setup and teardown code that runs in a class before tests are run we can have decorated class methods in camelCase To mock a web site request with patch() and check URL call and returned result from unittest.mock import patch class TestEmployee(unittest.TestCase): def test_monthly_schedule(self): with patch('employee.requests.get') as mocked_get: # set return values mocked_get.return_value.ok = True mocked_get.return_value.text = 'Success' # perform a mock call with emp_1 schedule = self.emp_1.monthly_schedule('May') mocked_get.assert_called_with('http://company.com/Schafer/May') self.assertEqual(schedule, 'Success') @classmethod def setUpClass(cls): print('setupClass') @classmethod def tearDownClass(cls): print('teardownClass') test_employee.py

Python unittest.mock example import unittest from unittest.mock import patch from employee import Employee class TestEmployee(unittest.TestCase): @classmethod def setUpClass(cls): print('setupClass') @classmethod def tearDownClass(cls): print('teardownClass') def setUp(self): print('\nsetUp') self.emp_1 = Employee('Corey', 'Schafer', 50000) self.emp_2 = Employee('Sue', 'Smith', 60000) def tearDown(self): print('tearDown') #… additional code here removed for readability def test_monthly_schedule(self): with patch('employee.requests.get') as mocked_get: mocked_get.return_value.ok = True mocked_get.return_value.text = 'Success' schedule = self.emp_1.monthly_schedule('May') mocked_get.assert_called_with('http://company.com/Schafer/May') self.assertEqual(schedule, 'Success') mocked_get.return_value.ok = False schedule = self.emp_2.monthly_schedule('June') mocked_get.assert_called_with('http://company.com/Smith/June') self.assertEqual(schedule, 'Bad Response!') if __name__ == '__main__': unittest.main(argv=['first-arg-is-ignored'], exit=False) import requests class Employee: """A sample Employee class""" raise_amt = 1.05 def __init__(self, first, last, pay): self.first = first self.last = last self.pay = pay @property def email(self): return 'f{self.first}.{self.last}@email.com' @property def fullname(self): return 'f{self.first} {self.last}' def apply_raise(self): self.pay = int(self.pay * self.raise_amt) #… additional code here removed for readability def monthly_schedule(self, month): response = requests.get(f'http://company.com/{self.last}/{month}') if response.ok: return response.text else: return 'Bad Response!' ----------- OUTPUT ----------- PS C:\python_unittesting> python .\test_employee.py -v setupClass test_apply_raise (__main__.TestEmployee) ... setUp test_apply_raise tearDown ok test_email (__main__.TestEmployee) ... setUp test_email tearDown ok test_fullname (__main__.TestEmployee) ... setUp test_fullname tearDown ok test_monthly_schedule (__main__.TestEmployee) ... setUp tearDown ok teardownClass ------------------------------------------------------ Ran 4 tests in 0.007s test_employee.py and employee.py

IoC, DIP, DI and IoC containers IoC – Loose coupling between classes DIP – High-level modules should not depend on low level modules. Both ... DI – A design pattern which implements the IoC principle to invert the creation of dependent objects IoC container – A framework used to manage automatic dependency injection https://www.tutorialsteacher.com/ioc/

Python and Dependency Injection 1 Dependency Injection is a pattern that decreases coupling and increases cohesion (forming a united whole) via the Dependency Inversion Principle The D in SOLI D : https://en.wikipedia.org/wiki/Dependency_inversion_principle The DI design pattern implements the Inversion of Control (IoC) principle to resolve dependencies: https://en.wikipedia.org/wiki/Dependency_injection If object A (a client) depends on object B (a service), object A must not create or import object B directly - Instead object A must provide a way to inject object B The responsibilities of objects creation and dependency injection are usually delegated to some external code - the dependency injector There are several ways to inject a service (B) into a client (A) by passing it (B) as an __init__ argument (constructor / initializer injection) by setting it (B) as an attribute’s value (attribute injection) by passing it (B) as a method’s argument (method injection) di_python.py di_python_factory.py vehicle.py

Python and Dependency Injection 2 The dependency injection pattern has a few strict rules that should be followed The client (A) delegates to the dependency injector the responsibility of injecting its dependencies - the services (B) The client should not know how to create the service, it should only know the interface of the service The service should not know about that it is used by the client The dependency injector knows how to create the client and the service It also knows that the client depends on the service, and knows how to inject the service into the client The client and service should know nothing about the dependency injector Python Dependency Dependency Injector https://github.com/ets-labs/python-dependency-injector Python IoC Container https://github.com/eyaldror/di_container di_python.py di_python_factory.py vehicle.py

Dependency Injection (Dis)Advantages Advantages Control of application structure Decreased coupling of application components Increased code reusability Increased testability Increased maintainability Reconfiguration of a system without rebuilding Disadvantages One need to explicitly specify the dependencies Some extra work in the beginning

Recommended viewing and reading Python Mocks: a gentle introduction - Part 1 and 2 http://www.thedigitalcatonline.com/blog/2016/03/06/python-mocks-a-gentle-introduction-part-1/ Python 3.x unittest.mock documentation https://docs.python.org/3/library/unittest.mock.html What the mock?  - A cheatsheet for mocking in Python https://medium.com/@yeraydiazdiaz/what-the-mock-cheatsheet-mocking-in-python-6a71db997832 https://github.com/yeraydiazdiaz/wtmock Test Driven Development (TDD) with Python – Mock Objects https://rubikscode.net/2019/03/11/test-driven-development-tdd-with-python-mock-objects/ Python Tutorial: Unit Testing Your Code with the unittest Module https://www.youtube.com/watch?v=6tNS--WetLI Demystifying the Patch Function https://www.youtube.com/watch?v=ww1UsGZV8fQ