-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
298: Mocking External Dependencies with unittest.mock
I've seen a lot of developers approach testing external dependencies by simply creating a "test" account on the service they're using—like a Stripe sandbox or a Mailgun test domain—and hitting the real API during their unit tests. On the surface, it feels more "authentic." You're testing the real plumbing, right? But in a professional CI/CD pipeline, this is a recipe for disaster.
import requests
class PaymentProcessor:
def charge_customer(self, customer_id, amount):
# Imagine this hits a real production-like API
response = requests.post(
"https://api.payments-gateway.com/v1/charge",
json={"customer": customer_id, "amount": amount}
)
return response.json().get("status") == "success"
The cost of hitting the real wire
If you write a test that actually calls that charge_customer method, you've just turned your unit test into an integration test. Now, your test suite's success depends on the network being up, the API gateway not being throttled, and your API keys being correctly configured in the environment. If the payment gateway goes down for maintenance at 3:00 AM, your build pipeline fails, and your team gets paged—not because your code is broken, but because someone else's server is rebooting. It's slow, it's flaky, and frankly, it's annoying.
Replacing the dependency with a double
This is where unittest.mock comes in. Instead of letting the code actually travel across the internet, we "patch" the function or class that handles the network call. We replace it with a Mock object that looks like the real thing but exists entirely in memory. I prefer the patch decorator because it handles the cleanup for you, swapping the real object back in once the test finishes.
from unittest import TestCase
from unittest.mock import patch
from my_app import PaymentProcessor
class TestPaymentProcessor(TestCase):
@patch('my_app.requests.post')
def test_charge_customer_success(self, mock_post):
# We tell the mock exactly what to return when it's called
mock_post.return_value.json.return_value = {"status": "success"}
processor = PaymentProcessor()
result = processor.charge_customer("cust_123", 100)
self.assertTrue(result)
# We can also verify the code actually sent the right data
mock_post.assert_called_once_with(
"https://api.payments-gateway.com/v1/charge",
json={"customer": "cust_123", "amount": 100}
)
Controlling the chaos
The real power of mocking isn't just about speed; it's about testing the "untestable" scenarios. How do you test how your app behaves when the API returns a 500 Internal Server Error? Or a 429 Too Many Requests? Trying to force a third-party API to crash on command is nearly impossible. With a mock, it's a one-liner.
By setting the side_effect attribute of a mock, you can make it raise an exception or return different values on consecutive calls. This allows you to verify that your error-handling logic—the try/except blocks you spent hours writing—actually works as intended.
When to stop mocking
A word of caution: don't mock everything. If you mock your entire database layer and every single internal utility function, you're essentially testing that Python works, not that your logic works. I generally follow a rule of thumb: mock the boundaries of your system. Mock the API calls, the filesystem, and the clock. Keep the internal business logic real. If you find yourself mocking five different things just to get one test to run, that's usually a signal that your function is doing too much and needs to be broken down.
📋 Practical Task
Exercise: Simulating a Weather API Timeout
You are working on a system that sends emergency alerts based on weather data. The system uses a WeatherClient class that calls a remote API. You need to ensure that the system doesn't crash when the API times out; instead, it should return a fallback value of "Unknown".
Your task: Write a test case using unittest.mock.patch that simulates a requests.exceptions.Timeout exception being raised by the requests.get method. Verify that the get_current_temperature method handles this exception and returns "Unknown".
# code_to_test.py
import requests
class WeatherClient:
def get_current_temperature(self, city):
try:
response = requests.get(f"https://api.weather.com/v1/{city}")
response.raise_for_status()
return response.json().get("temp")
except (requests.exceptions.RequestException, requests.exceptions.Timeout):
return "Unknown"
Implement the test class in a separate file, ensuring you correctly patch requests.get and use side_effect to trigger the timeout.
There are no comments for now.