1. Why We Need Mock
When writing unit tests, the function under test is sometimes not a self-contained executable unit. It depends on external resources, such as the return value of another function or a value in a row of a database.
To shield the test from interference from external dependencies, we use Mock techniques. By simulating the test resources, we satisfy the dependency conditions.
From the perspective of design patterns, for functions and classes that satisfy the single responsibility principle, it is also reasonable to use Mock to ignore external dependencies during testing. This is because testing external dependencies should be done inside those dependencies themselves, and should not be shifted onto the caller.
By the same reasoning, if unit tests are hard to write, it is quite likely that the software has not followed a certain design pattern in practice. In that case, the most important thing is to make the software conform to the conventions of design patterns, rather than rushing into unit tests.
2. Differences Between Stub, Fake, and Mock
In fact, Mock is not the only way to simulate test resources; there are similar approaches such as Stub and Fake.
A Stub provides a set of method interfaces for the test object, simulating the real test resource. When the test object calls a Stub method, the Stub responds with a predetermined result and may also produce a predetermined error or exception. A Stub can track the interactions with the test object, but it does not process the incoming or outgoing data.
A Fake also provides a set of method interfaces for tracking the interactions with the test object, but unlike a Stub, a Fake processes the input data and produces results based on it.
Using either Stub or Fake, you can perform state verification on the test object. However, if you want to know whether the test object called the methods in the correct order, you need behavior verification.
Mock can be used for behavior verification of the test object.
3. Mock in Python
Before Python 3.3, using Mock required installing it from a third party first:
| |
Starting with Python 3.3, Mock was introduced into the standard library under the name unittest.mock.
Let us look at a simple example (the following all use Python 2.7 as an example):
| |
The approach to testing with Mock:
- Find the external dependencies of the object under test. These may be a function, object, class, and so on.
- Instantiate the Mock class to get a Mock object, and set the Mock object’s behavior to match the dependency. For example, its return value, object value, and so on.
- Use the Mock object to replace the external dependency.
- Write the test code and the assertions that check whether the result matches expectations.
4. Using Mock, MagicMock, and patch
4.1 Mock
The Mock class definition:
| |
Important parameters:
- return_value, the value returned when the mock object is called and the side_effect function returns DEFAULT.
- side_effect, this parameter points to a callable or iterable object. When the mock object is called, if the function’s return value is not DEFAULT, then that function’s return value is used as the return value of the mock object call.
side_effect has three usages:
1, Return values in sequence
| |
2, Call a function to process the return value
| |
3, Actively raise an exception
| |
4.2 MagicMock
The MagicMock class definition:
| |
MagicMock is a subclass of the Mock class, implementing many magic methods and attributes.
Look at an example:
| |
When creating a Mock object, the __iter__ function is not implemented by default, so it raises an error. But the MagicMock object adds this magic method.
Normally, unless you are mocking an iterable object, you will not notice the difference between Mock and MagicMock.
4.3 patch
patch is a method provided by Mock for replacing certain functions and attributes.
patch definition:
| |
Important parameters:
- target, the string name of the function being replaced; you need to provide the full path
- new, the Mock instance to replace it with, MagicMock by default
There are usually two ways to write it:
- Use with to control the scope of the context in which the replacement happens
| |
- Use a decorator to replace the specified attribute or function
| |
patch also has three very useful extensions:
- patch.dict for replacing dictionaries
| |
- patch.multiple for multiple replacements
| |
- patch.object for replacing instances
| |
5. Assertions
Assertions are used to check whether the test meets expectations. Mock-related assertions:
- assert_not_called, was never called
- assert_called_with, was called
- assert_called_once_with, was called only once
- aseert_has_calls, was called in the specified order
- assert_any_calls, was called at any point
- reset_mock, resets the call records
Usage example:
| |
