When you’re writing robust code, tests are essential for verifying that your application logic is correct, reliable, and efficient. However, the value of your tests depends on how well they demonstrate these criteria. Obstacles such as complex logic and unpredictable dependencies make writing valuable tests difficult. The Python mock object library, unittest.mock, can help you overcome these obstacles.
By the end of this article, you’ll be able to:
Mockpatch()You’ll begin by seeing what mocking is and how it will improve your tests.
A mock object substitutes and imitates a real object within a testing environment. It is a versatile and powerful tool for improving the quality of your tests.
One reason to use Python mock objects is to control your code’s behavior during testing.
For example, if your code makes HTTP requests to external services, then your tests execute predictably only so far as the services are behaving as you expected. Sometimes, a temporary change in the behavior of these external services can cause intermittent failures within your test suite.
Because of this, it would be better for you to test your code in a controlled environment. Replacing the actual request with a mock object would allow you to simulate external service outages and successful responses in a predictable way.
Sometimes, it is difficult to test certain areas of your codebase. Such areas include except blocks and if statements that are hard to satisfy. Using Python mock objects can help you control the execution path of your code to reach these areas and improve your code coverage.
Another reason to use mock objects is to better understand how you’re using their real counterparts in your code. A Python mock object contains data about its usage that you can inspect such as:
Understanding what a mock object does is the first step to learning how to use one.
Now, you’ll see how to use Python mock objects.
The Python mock object library is unittest.mock. It provides an easy way to introduce mocks into your tests.
unittest.mock provides a class called Mock which you will use to imitate real objects in your codebase. Mock offers incredible flexibility and insightful data. This, along with its subclasses, will meet most Python mocking needs that you will face in your tests.
The library also provides a function, called patch(), which replaces the real objects in your code with Mock instances. You can use patch() as either a decorator or a context manager, giving you control over the scope in which the object will be mocked. Once the designated scope exits, patch() will clean up your code by replacing the mocked objects with their original counterparts.
Finally, unittest.mock provides solutions for some of the issues inherent in mocking objects.
Now, you have a better understanding of what mocking is and the library you’ll be using to do it. Let’s dive in and explore what features and functionalities unittest.mock offers.
Mock Objectunittest.mock offers a base class for mocking objects called Mock. The use cases for Mock are practically limitless because Mock is so flexible.
Begin by instantiating a new Mock instance:
Python
Now, you are able to substitute an object in your code with your new Mock. You can do this by passing it as an argument to a function or by redefining another object:
Python
When you substitute an object in your code, the Mock must look like the real object it is replacing. Otherwise, your code will not be able to use the Mock in place of the original object.
For example, if you are mocking the json library and your program calls dumps(), then your Python mock object must also contain dumps().
Next, you’ll see how Mock deals with this challenge.
A Mock must simulate any object that it replaces. To achieve such flexibility, it creates its attributes when you access them:
Python
Since Mock can create arbitrary attributes on the fly, it is suitable to replace any object.
Using an example from earlier, if you’re mocking the json library and you call dumps(), the Python mock object will create the method so that its interface can match the library’s interface:
Python
Notice two key characteristics of this mocked version of dumps():
Unlike the real dumps(), this mocked method requires no arguments. In fact, it will accept any arguments that you pass to it.
The return value of dumps() is also a Mock. The capability of Mock to recursively define other mocks allows for you to use mocks in complex situations:
Python
Because the return value of each mocked method is also a Mock, you can use your mocks in a multitude of ways.
Mocks are flexible, but they’re also informative. Next, you’ll learn how you can use mocks to understand your code better.
Mock instances store data on how you used them. For instance, you can see if you called a method, how you called the method, and so on. There are two main ways to use this information.
First, you can assert that your program used an object as you expected:
Python
.assert_called() ensures you called the mocked method while .assert_called_once() checks that you called the method exactly one time.
Both assertion functions have variants that let you inspect the arguments passed to the mocked method:
.assert_called_with(*args, **kwargs).assert_called_once_with(*args, **kwargs)To pass these assertions, you must call the mocked method with the same arguments that you pass to the actual method:
Python
json.loads.assert_called_with('{"key": "value"}') raised an AssertionError because it expected you to call loads() with a positional argument, but you actually called it with a keyword argument. json.loads.assert_called_with(s='{"key": "value"}') gets this assertion correct.
Second, you can view special attributes to understand how your application used an object:
Python
You can write tests using these attributes to make sure that your objects behave as you intended.
Now, you can create mocks and inspect their usage data. Next, you’ll see how to customize mocked methods so that they become more useful in your testing environment.
One reason to use mocks is to control your code’s behavior during tests. One way to do this is to specify a function’s return value. Let’s use an example to see how this works.
First, create a file called my_calendar.py. Add is_weekday(), a function that uses Python’s datetime library to determine whether or not today is a week day. Finally, write a test that asserts that the function works as expected:
Python
Since you’re testing if today is a weekday, the result depends on the day you run your test:
Shell
If this command produces no output, the assertion was successful. Unfortunately, if you run the command on a weekend, you’ll get an AssertionError:
Shell
When writing tests, it is important to ensure that the results are predictable. You can use Mock to eliminate uncertainty from your code during testing. In this case, you can mock datetime and set the .return_value for .today() to a day that you choose:
Python
In the example, .today() is a mocked method. You’ve removed the inconsistency by assigning a specific day to the mock’s .return_value. That way, when you call .today(), it returns the datetime that you specified.
In the first test, you ensure tuesday is a weekday. In the second test, you verify that saturday is not a weekday. Now, it doesn’t matter what day you run your tests on because you’ve mocked datetime and have control over the object’s behavior.
When building your tests, you will likely come across cases where mocking a function’s return value will not be enough. This is because functions are often more complicated than a simple one-way flow of logic.
Sometimes, you’ll want to make functions return different values when you call them more than once or even raise exceptions. You can do this using .side_effect.
You can control your code’s behavior by specifying a mocked function’s side effects. A .side_effect defines what happens when you call the mocked function.
To test how this works, add a new function to my_calendar.py:
Python
get_holidays() makes a request to the localhost server for a set of holidays. If the server responds successfully, get_holidays() will return a dictionary. Otherwise, the method will return None.
You can test how get_holidays() will respond to a connection timeout by setting requests.get.side_effect.
For this example, you’ll only see the relevant code from my_calendar.py. You’ll build a test case using Python’s unittest library:
Python
You use .assertRaises() to verify that get_holidays() raises an exception given the new side effect of get().
Run this test to see the result of your test:
Shell
If you want to be a little more dynamic, you can set .side_effect to a function that Mock will invoke when you call your mocked method. The mock shares the arguments and return value of the .side_effect function:
Python
First, you created .log_request(), which takes a URL, logs some output using print(), then returns a Mock response. Next, you set the .side_effect of get() to .log_request(), which you’ll use when you call get_holidays(). When you run your test, you’ll see that get() forwards its arguments to .log_request() then accepts the return value and returns it as well:
Shell
Great! The print() statements logged the correct values. Also, get_holidays() returned the holidays dictionary.
.side_effect can also be an iterable. The iterable must consist of return values, exceptions, or a mixture of both. The iterable will produce its next value every time you call your mocked method. For example, you can test that a retry after a Timeout returns a successful response:
Python
The first time you call get_holidays(), get() raises a Timeout. The second time, the method returns a valid holidays dictionary. These side effects match the order they appear in the list passed to .side_effect.
You can set .return_value and .side_effect on a Mock directly. However, because a Python mock object needs to be flexible in creating its attributes, there is a better way to configure these and other settings.
You can configure a Mock to set up some of the object’s behaviors. Some configurable members include .side_effect, .return_value, and .name. You configure a Mock when you create one or when you use .configure_mock().
You can configure a Mock by specifying certain attributes when you initialize an object:
Python
While .side_effect and .return_value can be set on the Mock instance, itself, other attributes like .name can only be set through .__init__() or .configure_mock(). If you try to set the .name of the Mock on the instance, you will get a different result:
Python
.name is a common attribute for objects to use. So, Mock doesn’t let you set that value on the instance in the same way you can with .return_value or .side_effect. If you access mock.name you will create a .name attribute instead of configuring your mock.
You can configure an existing Mock using .configure_mock():
Python
By unpacking a dictionary into either .configure_mock() or Mock.__init__(), you can even configure your Python mock object’s attributes. Using Mock configurations, you could simplify a previous example:
Python
Now, you can create and configure Python mock objects. You can also use mocks to control the behavior of your application. So far, you’ve used mocks as arguments to functions or patching objects in the same module as your tests.
Next, you’ll learn how to substitute your mocks for real objects in other modules.
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.
patch() as a DecoratorIf you want to mock an object for the duration of your entire test function, you can use patch() as a function decorator.
To see how this works, reorganize your my_calendar.py file by putting the logic and tests into separate files:
Python
These functions are now in their own file, separate from their tests. Next, you’ll re-create your tests in a file called tests.py.
Up to this point, you’ve monkey patched objects in the file in which they exist. Monkey patching is the replacement of one object with another at runtime. Now, you’ll use patch() to replace your objects in my_calendar.py:
Python
Originally, you created a Mock and patched requests in the local scope. Now, you need to access the requests library in my_calendar.py from tests.py.
For this case, you used patch() as a decorator and passed the target object’s path. The target path was 'my_calendar.requests' which consists of the module name and the object.
You also defined a new parameter for the test function. patch() uses this parameter to pass the mocked object into your test. From there, you can modify the mock or make assertions as necessary.
You can execute this test module to ensure it’s working as expected:
Shell
Using patch() as a decorator worked well in this example. In some cases, it is more readable, more effective, or easier to use patch() as a context manager.
patch() as a Context ManagerSometimes, you’ll want to use patch() as a context manager rather than a decorator. Some reasons why you might prefer a context manager include the following:
To use patch() as a context manager, you use Python’s with statement:
Python
When the test exits the with statement, patch() replaces the mocked object with the original.
Until now, you’ve mocked complete objects, but sometimes you’ll only want to mock a part of an object.
Let’s say you only want to mock one method of an object instead of the entire object. You can do so by using patch.object().
For example, .test_get_holidays_timeout() really only needs to mock requests.get() and set its .side_effect to Timeout:
Python
In this example, you’ve mocked only get() rather than all of requests. Every other attribute remains the same.
object() takes the same configuration parameters that patch() does. But instead of passing the target’s path, you provide the target object, itself, as the first parameter. The second parameter is the attribute of the target object that you are trying to mock. You can also use object() as a context manager like patch().
Learning how to use patch() is critical to mocking objects in other modules. However, sometimes it’s not obvious what the target object’s path is.
Knowing where to tell patch() to look for the object you want mocked is important because if you choose the wrong target location, the result of patch() could be something you didn’t expect.
Let’s say you are mocking is_weekday() in my_calendar.py using patch():
Python
First, you import my_calendar.py. Then you patch is_weekday(), replacing it with a Mock. Great! This is working as expected.
Now, let’s change this example slightly and import the function directly:
Python
Notice that even though the target location you passed to patch() did not change, the result of calling is_weekday() is different. The difference is due to the change in how you imported the function.
from my_calendar import is_weekday binds the real function to the local scope. So, even though you patch() the function later, you ignore the mock because you already have a local reference to the un-mocked function.
A good rule of thumb is to patch() the object where it is looked up.
In the first example, mocking 'my_calendar.is_weekday()' works because you look up the function in the my_calendar module. In the second example, you have a local reference to is_weekday(). Since you use the function found in the local scope, you should mock the local function:
Python
Now, you have a firm grasp on the power of patch(). You’ve seen how to patch() objects and attributes as well as where to patch them.
Next, you’ll see some common problems inherent in object mocking and the solutions that unittest.mock provides.
Mocking objects can introduce several problems into your tests. Some problems are inherent in mocking while others are specific to unittest.mock. Keep in mind that there are other issues with mocking that are not mentioned in this tutorial.
The ones covered here are similar to each other in that the problem they cause is fundamentally the same. In each case, the test assertions are irrelevant. Though the intention of each mock is valid, the mocks themselves are not.
Classes and function definitions change all the time. When the interface of an object changes, any tests relying on a Mock of that object may become irrelevant.
For example, you rename a method but forget that a test mocks that method and invokes .assert_not_called(). After the change, .assert_not_called() is still True. The assertion is not useful, though, because the method no longer exists.
Irrelevant tests may not sound critical, but if they are your only tests and you assume that they work properly, the situation could be disastrous for your application.
A problem specific to Mock is that a misspelling can break a test. Recall that a Mock creates its interface when you access its members. So, you will inadvertently create a new attribute if you misspell its name.
If you call .asert_called() instead of .assert_called(), your test will not raise an AssertionError. This is because you’ve created a new method on the Python mock object named .asert_called() instead of evaluating an actual assertion.
These problems occur when you mock objects within your own codebase. A different problem arises when you mock objects interacting with external codebases.
Imagine again that your code makes a request to an external API. In this case, the external dependency is the API which is susceptible to change without your consent.
On one hand, unit tests test isolated components of code. So, mocking the code that makes the request helps you to test your isolated components under controlled conditions. However, it also presents a potential problem.
If an external dependency changes its interface, your Python mock objects will become invalid. If this happens (and the interface change is a breaking one), your tests will pass because your mock objects have masked the change, but your production code will fail.
Unfortunately, this is not a problem that unittest.mock provides a solution for. You must exercise judgment when mocking external dependencies.
All three of these issues can cause test irrelevancy and potentially costly issues because they threaten the integrity of your mocks. unittest.mock gives you some tools for dealing with these problems.
As mentioned before, if you change a class or function definition or you misspell a Python mock object’s attribute, you can cause problems with your tests.
These problems occur because Mock creates attributes and methods when you access them. The answer to these issues is to prevent Mock from creating attributes that don’t conform to the object you’re trying to mock.
When configuring a Mock, you can pass an object specification to the spec parameter. The spec parameter accepts a list of names or another object and defines the mock’s interface. If you attempt to access an attribute that does not belong to the specification, Mock will raise an AttributeError:
Python
Here, you’ve specified that calendar has methods called .is_weekday() and .get_holidays(). When you access .is_weekday(), it returns a Mock. When you access .create_event(), a method that does not match the specification, Mock raises an AttributeError.
Specifications work the same way if you configure the Mock with an object:
Python
.is_weekday() is available to calendar because you configured calendar to match the my_calendar module’s interface.
Furthermore, unittest.mock provides convenient methods of automatically specifying a Mock instance’s interface.
One way to implement automatic specifications is create_autospec:
Python
Like before, calendar is a Mock instance whose interface matches my_calendar. If you’re using patch(), you can send an argument to the autospec parameter to achieve the same result:
Python
You’ve learned so much about mocking objects using unittest.mock!
Now, you’re able to:
Mock to imitate objects in your testspatch() objects throughout your codebaseYou have built a foundation of understanding that will help you build better tests. You can use mocks to gain insights into your code that you would not have been able to get otherwise.
I leave you with one final disclaimer. Beware of overusing mock objects!
It’s easy to take advantage of the power of Python mock objects and mock so much that you actually decrease the value of your tests.
If you’re interested in learning more about unittest.mock, I encourage you to read its excellent documentation.