Python
Completed
-
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
299: Patching Objects in Tests
Alright, let's look at a real-world headache: external dependencies. Imagine we've written a small service that fetches the current temperature from a third-party API. It's simple code, but it's a nightmare to test because we can't control the weather, and we certainly don't want our CI/CD pipeline failing just because a server in Virginia decided to go offline for five minutes.
# weather_service.py
import requests
def get_temperature(city):
# In a real app, you'd have an API key here
response = requests.get(f"https://api.weather.com/v1/{city}")
data = response.json()
return data["temp"]
The "It Works on My Machine" API Trap
My first instinct when testing this is to just call the function. But look what happens when I write a basic test for it:
# test_weather.py
from weather_service import get_temperature
def test_get_temperature():
temp = get_temperature("London")
assert temp == 15
This is a fragile test. If the actual temperature in London is 16, the test fails. If the API is down, the test crashes. This isn't testing my logic; it's testing the internet. We need a way to "trick" get_temperature into thinking it's talking to the API, while we actually provide a canned response.
Swapping the Real Deal for a Double
This is where unittest.mock.patch comes in. The goal is to replace the requests.get function with a "Mock" object that we control. Let's try to just wrap the test in a patch decorator:
from unittest.mock import patch
from weather_service import get_temperature
@patch('requests.get')
def test_get_temperature(mock_get):
# I'll tell the mock what to return when it's called
mock_get.return_value.json.return_value = {"temp": 15}
temp = get_temperature("London")
assert temp == 15
mock_get.assert_called_once_with("https://api.weather.com/v1/London")
I ran this, and... it failed. Specifically, the real requests.get was still being called. Why? This is the most common stumbling block with patching in Python. I patched 'requests.get', but weather_service.py had already imported requests when the program started. It's holding onto its own reference to the real library.
Where Exactly Are We Patching?
The rule of thumb in Python is: patch where the object is looked up, not where it is defined.
Since get_temperature is inside weather_service.py, that's where the lookup happens. I don't want to patch the global requests library; I want to patch the version of requests that exists inside the weather_service module. Let's adjust the path:
# Change this:
@patch('requests.get')
# To this:
@patch('weather_service.requests.get')
Now, when get_temperature calls requests.get, it's not looking at the actual library—it's looking at the mock I've injected into that specific module. I ran it again, and it passed instantly. No network calls, no API keys, and total control over the data.
One last thing: notice how I used mock_get.return_value.json.return_value? That looks ridiculous, I know. But remember that requests.get() returns a Response object, and that object has a .json() method, which in turn returns a dictionary. To mock a chain of calls, you have to chain .return_value for every step of the process.
📋 Practical Task
Simulating a Flaky Payment Gateway
You are testing a PaymentProcessor class that uses a library called stripe_client to charge customers. The charge method in stripe_client can either return a success dictionary or raise a ConnectionError if the gateway is down.
Your Task: Write a test for the process_payment function that simulates a gateway failure. You must patch the stripe_client.charge method so that instead of making a real network call, it raises a ConnectionError. Verify that your process_payment function catches this error and returns the string "Gateway Timeout".
Starter Code:
# payment_system.py
import stripe_client
def process_payment(amount):
try:
stripe_client.charge(amount)
return "Success"
except ConnectionError:
return "Gateway Timeout"
# Your test file:
# Use unittest.mock.patch to make stripe_client.charge raise ConnectionError
There are no comments for now.