Master

Testing the seam, not the world

A function that talks to the network made my test slow and flaky, and it charged the real service every run. A mock replaces the outside world with a stand-in you control, so the test checks your handling, not the world.

What you will see

>>>

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Mock a network call so the test runs offline and can force an error branch.

1

A function that fetches and reads a field. Test it with a fake response instead of the network.

import monkeypatch def fetch_name(client): return client.get("/u")["name"]

Before you run it, say out loud what it will print. Then open the answer.

What it prints
>>>
2

Replace get with a fake that returns a fixed body. The test is now offline and instant.

def fake_get(path): return {"name": "ada"} monkeypatch.setattr(client, "get", fake_get) assert fetch_name(client) == "ada"

Before you run it, say out loud what it will print. Then open the answer.

What it prints
>>>
3

Force the error branch on command. The retry path is now testable without the real service failing.

def boom(path): raise ConnectionError monkeypatch.setattr(client, "get", boom) assert retry(fetch_name) == "ada"

Before you run it, say out loud what it will print. Then open the answer.

What it prints
>>>
Never let a unit test hit the real network. It is slow, flaky, and it mutates real state. The mock is what makes the test about your code and not the world.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A mock replaces an outside call with a stand-in you control, so the test runs offline and deterministically and can force the failure paths.
1 Replace the outside call
↓
2 Test runs offline and fast
↓
3 You choose the response
↓
4 Failure paths become reachable
Why this works
Isolate the seam

A unit test should test your code, not the network, the clock, or a random source. Replace those with a mock so the test is fast, offline, and deterministic. The test then checks how your code reacts to a response you control, which is the part you own.

Mock the failure too

The value of a mock is that you can make it fail whenever you want. Error handling, retries, and timeouts are branches you cannot reach by waiting for the real world to misbehave. A mock reaches them on command, which is how you test the paths that matter most in production.

Examples

Swap the outside

monkeypatch.setattr(requests, "get", fake_get)

This replaces the real call with a fake you wrote. The test now runs instantly, offline, and returns exactly the response you choose.

Force the hard path

def fake_get(url):
    raise ConnectionError

You can make the world fail on command. Testing the retry or the error branch becomes possible without waiting for the real service to break.

Assert the call

assert called_with == "https://api/v1"

Sometimes the promise is about how the world was asked, not the answer. A mock records the call so you can assert the right request was made.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Testing the rule, not the table
Previous Lesson
Testing the rule, not the table
Next Lesson
Running the suite on every change
Running the suite on every change →