Module 8: Project Contract And Integration For Reservo
3. Verify the contract against the `FakeBookingRepository`
Description
You have the contract written; now you start verifying it, and you start with the most comfortable side: the fake. In this lesson you run the battery against the FakeBookingRepository and see the four clauses in green. It's the first side of the second deliverable —the verification from both sides—, and it's a real step: it confirms that the fake, the double all your fast unit tests use, meets the four promises the consumer needs. A fake that failed a clause of the contract would be a fake that lies, and you'd know it here.
But this lesson has a second half, more important than the first, and it's the one that separates whoever understands the capstone from whoever just copies it: the green against the fake, alone, proves nothing. It's necessary, yes; sufficient, no. A contract you've only seen pass against the fake is a contract that asked the fake whether the fake is right, and the fake, which is the assumption under examination, always answers yes. That's exactly module 2's lie in green. The verification against the fake certifies that your contract is coherent with the double; it says nothing about whether the double coincides with the real thing. For that you still have to cross the seam with the SqliteBookingRepository, which is lesson 4. Here you see the first green and learn to distrust it just enough.
Connection to the module: this lesson delivers the first half of deliverable 2 and sets up the tension lesson 4 resolves. The battery you wrote in lesson 2 runs here against [fake] and in lesson 4 against [fake] and [sqlite] together. Today's goal isn't just to see four greens: it's to understand why those four greens, without the other side, are an incomplete photo —the simulator confirming itself—. With that distrust installed, lesson 4's green on both sides will carry the weight it deserves.
Analogy: grading your own exam with your own answer key
Imagine you're studying for an exam and, to practice, you write the questions and the answer key yourself. You solve your exam, grade yourself with your own key, and score a hundred. What did you prove? That you're coherent with yourself: your answers match what you believed was correct. You didn't prove you know the subject —if you misunderstood a topic, your question and your answer share the same error, and your self-grading blesses it—. The hundred is real, but it measures a single thing: that you didn't contradict yourself. To know whether you really know, you need an exam you didn't write, graded against an external key —the official standard—.
Verifying the contract against the fake is grading yourself with your own key. You wrote the fake, with the same assumptions you put in the contract; when the contract passes against the fake, you confirm that your double is coherent with your clauses, nothing more. If your assumption about the repository was wrong, the fake shares it and the contract blesses it: green, and false. The external standard —the official key— is the real SqliteBookingRepository, the piece that actually runs in production, which you didn't write to pass your exam and which behaves according to SQLite, not according to your assumptions. That's why the green against the fake is the hundred of your self-exam: necessary to continue, but not the proof that you know. The proof arrives when the same contract passes against the real one.
Worked example: the four [fake] cases in green
Let's run the battery, but only the fake's side. Pytest lets you select cases by their id with -k, so -k fake runs the four clauses against [fake] and leaves out the [sqlite] ones. It's the way to look at a single side of the contract:
What to expect. On my machine (Python 3.14.0, pytest 9.1.1):
python3 -m pytest tests/test_repository_contract.py -k fake -v
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
collected 8 items / 4 deselected / 4 selected
tests/test_repository_contract.py::test_save_then_get_returns_the_same_booking[fake] PASSED [ 25%]
tests/test_repository_contract.py::test_get_of_a_missing_id_raises[fake] PASSED [ 50%]
tests/test_repository_contract.py::test_saving_the_same_id_twice_updates_not_duplicates[fake] PASSED [ 75%]
tests/test_repository_contract.py::test_find_by_room_returns_only_that_rooms_bookings[fake] PASSED [100%]
======================= 4 passed, 4 deselected in 0.01s ========================
Four greens, and four deselected. Pytest collected the eight cases, ran the four [fake] ones and skipped the four [sqlite] ones —that's what 4 deselected says—. The contract's four clauses pass against the FakeBookingRepository: it saves and reads the same booking, raises KeyError on an absent id, updates instead of duplicating, and lists only the requested room. It's a legitimate and useful result: it confirms that the fake your unit tests use meets the consumer's contract. If a colleague had written the fake with .get() instead of [...] —returning None where it should raise—, clause 2 would be red here, and you'd know it. The green against the fake does catch a badly written fake.
Why this green, alone, isn't enough
And now the part that matters. That green certifies one precise thing, and only that: the fake is coherent with the contract you wrote. It says nothing about whether the fake coincides with the real SqliteBookingRepository. And that's exactly the question the contract exists to answer.
Think of it with the divergence the guide pursued since module 1: the datetime that the fake returns intact and the real one returns as text. Clause 1 —"save-and-read returns the same booking"— passes against the fake, because the fake saves the whole object and returns it identical, datetime included. Against the fake, repo.get("bk-1") == booking is true without effort. But if the SqliteBookingRepository had the bug of not reconstructing the datetime on read, the same clause would fail against [sqlite] —a Booking with a text start isn't equal to one with a datetime start—. The [fake] green didn't see that bug, not because the clause is weak, but because the fake doesn't serialize: the fake can't exhibit the problem only the real one has.
Here's the key, and it's module 2's lesson made method: the fake is at once the subject and the oracle. When you test the contract against the fake, you ask the fake whether the fake meets an assumption you wrote based on how you believed the repository behaves. If your assumption was false, the fake shares it —you wrote it with the same head— and the contract blesses it in green. The fake can't contradict the assumption because the fake is the assumption. That's why the green against the fake measures internal coherence, not truth. The truth requires an external reference: the real piece.
This doesn't degrade the step you took. Verifying the fake is necessary —a fake that fails the contract is a problem you want to see—, and it's half of deliverable 2. What this lesson asks you to understand is that it's half a proof. The other half, the one that turns "the fake is coherent with me" into "the fake coincides with the real thing", is running the same battery against [sqlite]. There the contract stops being your self-exam and becomes the comparison against the official standard.
A practical rule: never deliver a one-sided contract
From this comes a hard rule for the capstone, and for any contract testing you do afterward: a contract verified against a single implementation isn't a contract, it's a test of that implementation. The contract's power comes from running the same battery against two or more providers; with only one, there's no comparison, and without comparison there's no guarantee of coincidence. If in your delivery you saw only 4 passed with [fake] ids, you'd have a test of the fake, not a contract of the repository.
The correct way —and the one lesson 4 does— is to not filter with -k: run the whole battery, the eight cases, and see [fake] and [sqlite] side by side. This lesson's -k fake was a magnifying glass to look at one side and understand what it proves and what it doesn't; it's not how the contract is delivered. You had to see it isolated once to distrust it with knowledge; from lesson 4 on, always both sides together.
Common mistakes
Considering the contract verified with the fake's green. What happens: the battery is run against the fake, four greens come out, and one concludes "the contract passes, done". Why it happens: the green feels like a goal reached, and the fake is the fastest to run. How to detect it: ask yourself whether any of those four clauses touched the real SqliteBookingRepository. If they all used the fake, you didn't verify coincidence with the real thing —you verified coherence with your double—. How to fix it: the fake's green is half of deliverable 2, not the deliverable. Don't close until you run the same battery against [sqlite] and see both sides green together, which is lesson 4.
Believing a "well-written" fake makes the real side unnecessary. What happens: someone trusts their fake so much ("I wrote it carefully, it does exactly what the real one does") that they skip the verification against SQLite. Why it happens: a fake that's correct today feels permanent and faithful. How to detect it: module 1's datetime divergence didn't come from a careless fake —the fake was impeccable—; it came from SQLite serializing and the dict not. That gulf isn't closed by care. How to fix it: accept that the fake and the real one are distinct technologies and can diverge even if the fake is perfect. The only way to know they coincide is to run the contract against both. Care doesn't substitute for verification.
Reading 4 deselected as an error. What happens: 4 passed, 4 deselected appears and someone worries: "why were four tests skipped?". Why it happens: "deselected" sounds like something was omitted by accident. How to detect it: if you used -k fake, the four [sqlite] cases were deselected on purpose by the filter; it's not a failure, it's what you asked for. How to fix it: -k selects by name; 4 deselected confirms that the filter left out the four [sqlite] ones, as you wanted in order to look only at the fake's side. When you want the eight, remove the -k and run the whole battery —exactly what lesson 4 does—.
Exercises
Exercise 1 — What exactly did the green prove? You ran -k fake and got 4 passed. State, precisely, what that result asserts and what it does not assert. Use clause 1 (save-and-read) as a concrete example.
See solution
What it asserts: that the FakeBookingRepository meets the four clauses just as you wrote them. For clause 1, it asserts that in the fake, repo.save(booking) followed by repo.get("bk-1") returns an object equal to booking. It's coherence between your double and your contract: the fake contradicts none of your assumptions.
What it does NOT assert: that the real SqliteBookingRepository meets those clauses, nor —which is the same thing— that the fake coincides with the real one. For clause 1, the fake's green says nothing about whether the real one returns the same booking: the fake saves the whole object and returns it identical (datetime included) by its nature as a dict, while the real one serializes and could return the start as a str. That divergence lives on the [sqlite] side, which this result didn't touch.
The exact phrase: the green against the fake proves internal coherence (the fake agrees with the contract), not truth (the fake agrees with the real thing). The truth requires the real side, and it's lesson 4.
Exercise 2 — The fake that does fail the contract. A colleague wrote the fake with return self._store.get(booking_id) in get (with .get(), which returns None) instead of return self._store[booking_id]. Without running, say which [fake] case would go red and with what message, and why this demonstrates that the fake's green does have value.
See solution
It would go red on test_get_of_a_missing_id_raises[fake]. Clause 2 does with pytest.raises(KeyError): repo.get("does-not-exist"). With .get(), the fake returns None for an absent id instead of raising KeyError, so pytest.raises doesn't see the expected exception and reports Failed: DID NOT RAISE KeyError. The rest of the [fake] clauses would stay green.
Why this demonstrates that the fake's green has value: because the contract against the fake does catch a badly written fake —one that breaks a clause—. The fake's green isn't useless; it certifies that the fake is coherent with the contract, and that rules out module 2's careless fake. What the fake's green can't catch is a divergence the fake doesn't exhibit because of its technology (like the datetime serialization), because there the fake and the contract share the same assumption. In short: the fake's green catches errors within the double, but not the differences between the double and the real one. That's why it's necessary, and that's why it's not sufficient.
Exercise 3 — The exam and the key. Translate the self-exam analogy to the contract's verification: match each element with its equivalent and explain the match in one sentence. (a) writing the questions and the key yourself; (b) scoring a hundred on your self-exam; (c) the external official key; (d) a topic you misunderstood.
See solution
- (a) Writing the questions and the key yourself ↔ writing the fake and the contract. Both come from your same head and your same assumptions: the fake and the contract share an author, so they share biases.
- (b) Scoring a hundred on your self-exam ↔ the green of the contract against the fake. Both measure coherence with yourself —your answers agree with your key, your fake agrees with your contract—, not mastery of the subject or coincidence with the real thing.
- (c) The external official key ↔ the real
SqliteBookingRepository. It's the reference you didn't write to pass your exam: it behaves according to SQLite, not according to your assumptions, and that's why grading against it (running the contract against[sqlite]) does prove whether your double tells the truth. - (d) A topic you misunderstood ↔ a false assumption about the repository (e.g., "
getreturnsNone"). If your understanding is wrong, your question and your answer share the error and the self-grading blesses it; likewise, if your assumption is false, the fake shares it and the contract approves it in green. Only the external key —or the real provider— gives it away.
The moral: nobody throws out the self-exam —it's useful for practicing—, but nobody gets certified with it alone. It's contrasted against the official key. The same with the fake: it's verified, but it's not enough; it's also run against the real one.
Summary and next step
In this lesson you ran the contract against the FakeBookingRepository and saw the four [fake] clauses in green —4 passed, 4 deselected—, confirming that your unit tests' double meets the promises the consumer needs. And you learned the most important thing about this step: that green, alone, doesn't prove the fake coincides with the real thing. With the self-exam graded with your own key you understood why —the fake is at once the subject and the oracle, so it shares your assumptions and can't contradict them—, and you saw, with the datetime divergence, how a clause passes against the fake that doesn't serialize and could fail against the real one that does. You set the hard rule: a contract verified against a single implementation isn't a contract, it's a test of that implementation.
Before moving on you should be able to: state what the green of the contract against the fake proves and doesn't prove; explain why the fake can't catch a divergence its technology doesn't exhibit; and justify why the verification of a single side is never a complete delivery.
The side that turns your self-exam into a real certification is missing. In lesson 4 you run the complete battery —without a filter— and see [fake] and [sqlite] side by side, eight greens. There you'll understand the transitivity that closes deliverable 2: if the fake meets the contract and the real one meets the contract, the fake and the real one coincide, and the fake can no longer lie about anything the contract covers.
Resources
- pytest documentation — Selecting tests with
-k— how-k fakeselects the cases by their id and why4 deselectedappears; the magnifying glass with which you looked at a single side of the contract. - pytest documentation —
pytest.raises— the reference for the absent-id clause and for theDID NOT RAISEthat gives away a fake that returnsNonewhere it should raise. sqlite3— DB-API for SQLite (Python documentation) — the "external official key" against which the contract will be graded in lesson 4; the real provider that behaves according to SQLite, not according to your assumptions.- Module 2 of this guide — The double that lied — where it was established that the fake is at once the subject and the oracle; useful for seeing why the green against the fake measures coherence, not truth.