Mocking and Patch
Explore how to apply mocking and patching with Python's unittest.mock library to create controlled test environments. Learn to replace real dependencies with mock objects, simulate method calls and errors, and use patch to intercept imports. Understand key testing strategies to write fast, reliable, and isolated unit tests that verify your code behavior without external system dependencies.
Unit tests are meant to be fast, reliable, and repeatable. However, real-world applications often depend on external systems such as third-party APIs, databases, or the file system. If our tests interact with these real dependencies, they can become slow, flaky, or even dangerous; for example, failing because the network is unavailable or accidentally modifying important files.
These problems can be addressed using mocking. Mocking replaces real dependencies with controlled stand-ins during testing. This concept is similar to a stunt double in film production. A stunt double performs a scene in place of the actor, but is not the actor. Similarly, a mock object imitates the behavior of a real dependency without performing the external action.
In Python, the unittest.mock library provides tools to create these substitutes, letting us test our program’s logic in isolation while keeping the outside world untouched.
The Mock object
The foundation of Python’s mocking system is the Mock class. A Mock object acts as a flexible stand-in for real objects. When we call a method on a mock, it does not perform any real work. Instead, it records what happened, e.g., how many times the method was called, what arguments were passed, and in what order the calls occurred. This makes it ideal for verifying how our code interacts with external dependencies.
Automatic attribute chaining
One of the most powerful features of Mock is its ability to create attributes automatically. If we access an attribute that does not exist, such as my_mock.some_method, Python does not raise an AttributeError. Instead, the mock dynamically creates a new child Mock object and returns it.
This behavior allows us to simulate complex call chains without writing full implementations. For example, we can mimic expressions like response.json().get("key") even though neither json() nor get() actually exists on ...