Module 8: Project Contract And Integration For Reservo

1. Module introduction: Reservo's complete process

Description

You've reached the end of the guide, and this module is where everything comes together. Over seven modules you learned the pieces one by one: what distinguishes a unit test from an integration test (module 1), how a double can lie in green (module 2), how a consumer-driven contract fixes the behavior two components promise each other (module 3), how to verify it from both sides and catch a breaking change (module 4), how to integrate real components crossing the seam (module 5), how to test at the boundaries of the database, the file, and HTTP (module 6), and how to isolate integration tests so each one starts clean (module 7). Each piece you practiced alone. Now you're going to execute the whole process, from start to finish, over Reservo, and deliver three things that work together: a contract, its verification from both sides, and an end-to-end integration.

And there's a shift in focus that defines this module: here it's not measured how many tests you write, but the method. The question the capstone teaches you to answer isn't "how many assertions did I put?", but "what did I double, what did I leave real, and why?". An engineer who writes two hundred tests without knowing which seam deserves a contract and which deserves an integration has two hundred tests and no criterion. One who writes six tests knowing exactly why each one exists —this contract because here the fake can diverge, this integration because here the real flow triggers a bug the inspection doesn't see— has a suite that tells the truth. The capstone is the proof that you have that criterion. That's why the heart of this lesson, more than any test, is a table: what to double and what to test real.

Connection to the module: this lesson is the capstone's map. Here you're not going to write the deliverable yet —that's the work of lessons 2 to 7, which build it step by step—; you're going to see the complete process at a single glance and set the criterion that governs it. You'll get to know the three deliverables and how they fit, the decision table that summarizes the whole guide's golden rule, and you'll see, with real pytest output, the finished deliverable running green: the contract certifying the fake and the real, and the end-to-end integration exercising bookgetcancel. With that destination clear, each following lesson builds a part of it until, in lesson 8, you deliver it formally and we close the guide.

Analogy: the final walkthrough

In module 1 we used the image of the model and the building: each piece, isolated, meets its standard, but the building falls at the joints. Let's close the guide with the same construction, now finished. Before delivering a building and letting people in, it's not enough for the architect to swear that each beam and each column was calculated well. A final walkthrough is done: a final inspection, with the building complete and assembled, where the things that only exist when everything is together are verified. The tap is opened and it's checked that the water actually runs through the real pipes to the kitchen; the structure is loaded and it's watched that the joints hold; the panel is switched on and it's seen that current reaches every outlet. The walkthrough doesn't recalculate a beam's strength —that was already done, piece by piece—; it verifies that the pieces, joined, work as a system.

This capstone is Reservo's final walkthrough. Over seven modules you calculated and tested each piece. Now you do the final inspection of the complete system, and for it you use exactly the two instruments the guide gave you. The contract is the walkthrough's checklist: the list of what each joint must meet, verified the same against the fake piece and the real one, so nobody can deliver a pipe that "looks good" but carries no water. The end-to-end integration is opening the tap and watching the water run: the bookgetcancel flow traveling through BookingService and the real SqliteBookingRepository, crossing the seam as it will in production. At the end you sign the certificate —pytest's green output— knowing you didn't certify loose pieces, but a system that works together.

The three deliverables

The capstone asks you for three things, and they're not three loose tasks: they're three layers of the same guarantee. It's worth seeing them together from now, because each following lesson builds one.

Deliverable 1 — The consumer-driven contract. A parametrized battery of tests that fixes the behavior BookingService (the consumer) needs from BookingRepository (the provider): save-and-read returns the same booking, get of an absent id raises, save twice of the same id updates without duplicating, find_by_room returns only that room's bookings. It's module 3's shared spec, written from the consumer's real needs.

Deliverable 2 — The verification from both sides. The same contract running, without duplicating a line, against the FakeBookingRepository and against the real SqliteBookingRepository —both green—. This is module 4's guarantee: if the fake and the real meet the same clauses, the fake can't lie about anything the contract covers. Eight cases: four clauses times two providers.

Deliverable 3 — The end-to-end integration. A test that connects BookingService with the real SqliteBookingRepository and exercises the complete bookgetcancel flow crossing the seam, isolated with a fixture or a rollback so it starts clean. It's module 5's integration with module 7's isolation. It catches what the contract doesn't see: the bugs of use, not of form.

The three need each other. The contract only certifies each piece against a spec, clause by clause; it doesn't see the live collaboration. The integration only sees one concrete path of the flow; it doesn't certify each clause against each provider. Together they cover the two questions this guide exists to answer: does the fake lie? (contract) and do the real pieces work together? (integration).

The table: what to double and what to test real

Here's the criterion that governs the whole capstone, condensed. Faced with each of BookingService's collaborators, the decision to double it or leave it real isn't taste: it follows a rule. You double what's slow, non-deterministic, and external; you keep real the seam you're testing.

CollaboratorIn the contractIn the integrationWhy
BookingRepositoryReal (fake AND sqlite)Real (sqlite)It's the seam under test. The contract verifies it in both implementations; the integration crosses it for real. It's the only thing that's not doubled, because it's what you want to test.
PaymentGatewayDoubled (StubPaymentGateway)DoubledExternal: charging for real would touch a card and a third-party service. It's not the seam this project tests; it's doubled so it's free, fast, and without effects.
clockDoubled (FixedClock)DoubledNon-deterministic: the real time changes on every run, and cancel calculates the refund based on the notice. Freezing it turns each anchor (6000/3000/0) into a fixed datum of the test.
EmailSenderDoubled (SpyEmailSender)DoubledExternal and with a side effect: sending a real email would fill inboxes and depend on the network. It's doubled; if needed, the spy records the sends without sending them.

Read the table as the summary of the whole guide. The why column is what's evaluated in the capstone: not that you doubled the clock, but that you know how to say "I doubled it because it's non-deterministic and I want to choose which anchor I exercise". The repository is the only row that says "real" in both columns, and not by chance: it's the seam where the risk this guide teaches you to cover lives —the divergence between the fake and the real—, so it's what the contract verifies and what the integration crosses. Everything else is doubled, because testing BookingService against the repository doesn't require charging real cards or waiting for the clock to advance.

There's one more choice, of a higher level, that the table doesn't show and that lesson 2 develops: which seam deserves a contract. Reservo has two candidate seams —the repository and the payment gateway—. The capstone chooses the repository, because it's where the fake-vs-real divergence is richest and most dangerous (the datetime serialization, the get that raises or returns None, the commit that persists or not). Choosing where to put the effort is part of the method.

A glimpse of the finished deliverable

Before building it piece by piece, let's see the destination: the complete deliverable running green. It's the two files that summarize the capstone —the parametrized contract (deliverables 1 and 2) and the end-to-end integration (deliverable 3)— run together. Don't write this yet; it's the map, not the territory. We'll build it calmly in the following lessons.

python3 -m pytest tests/test_repository_contract.py tests/test_end_to_end_integration.py -v

What to expect. On my machine (Python 3.14.0, pytest 9.1.1):

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
collected 10 items

tests/test_repository_contract.py::test_save_then_get_returns_the_same_booking[fake] PASSED [ 10%]
tests/test_repository_contract.py::test_save_then_get_returns_the_same_booking[sqlite] PASSED [ 20%]
tests/test_repository_contract.py::test_get_of_a_missing_id_raises[fake] PASSED [ 30%]
tests/test_repository_contract.py::test_get_of_a_missing_id_raises[sqlite] PASSED [ 40%]
tests/test_repository_contract.py::test_saving_the_same_id_twice_updates_not_duplicates[fake] PASSED [ 50%]
tests/test_repository_contract.py::test_saving_the_same_id_twice_updates_not_duplicates[sqlite] PASSED [ 60%]
tests/test_repository_contract.py::test_find_by_room_returns_only_that_rooms_bookings[fake] PASSED [ 70%]
tests/test_repository_contract.py::test_find_by_room_returns_only_that_rooms_bookings[sqlite] PASSED [ 80%]
tests/test_end_to_end_integration.py::test_book_then_get_persists_the_booking PASSED [ 90%]
tests/test_end_to_end_integration.py::test_book_cancel_get_full_flow_against_real_sqlite PASSED [100%]

============================== 10 passed in 0.01s ===============================

Ten greens, and each one is a part of the deliverable. The first eight are the contract from both sides (deliverables 1 and 2): four clauses, each run against [fake] and against [sqlite], the fake and the real certified equal. The last two are the end-to-end integration (deliverable 3): bookget saving and re-reading a booking, and the complete bookcancelget flow crossing the seam against real SQLite. This output is, literally, Reservo's walkthrough certificate signed. Everything that comes in the next six lessons is learning to produce each line of this report —with judgment, not from memory— and to read it when something goes red.

The map of the eight lessons

The capstone is built in order, each lesson a brick of the deliverable:

LessonWhat you buildThe deliverable
1The complete process and the decision table (this one)The criterion
2The consumer-driven contract, clause by clauseDeliverable 1
3The verification against the fake, and why it's not enough aloneDeliverable 2 (side one)
4The verification against real SQLite, both sides greenDeliverable 2 (complete)
5The end-to-end integration bookgetcancelDeliverable 3
6The isolation with fixture and rollbackDeliverable 3 (robust)
7Catching a breaking change with the contractThe proof of value
8The formal statement, the rubric, and the guide's closeThe delivery

Notice that lesson 7 doesn't add a new deliverable: it demonstrates why it was worth building the other three, catching on your machine a change that without a contract would explode in production. And lesson 8 doesn't just collect the delivery: it closes the whole guide, with the summary of the eight modules and the map of where to continue in the Testing ecosystem.

Common mistakes

Measuring the capstone by number of tests instead of by method. What happens: someone writes thirty BookingService tests believing more is better, and leaves out the decision of what to double or the contract from both sides. Why it happens: quantity is visible and easy to produce; the criterion is invisible. How to detect it: if you can't point out, for each test, which seam it tests and why you doubled what you doubled, you have volume without method. How to fix it: the capstone asks for three well-chosen deliverables, not a mountain of assertions. A four-clause contract verified from both sides and a well-isolated end-to-end integration are worth more than fifty unit tests against the fake. What's evaluated is that you can say why each test exists.

Believing the contract makes the integration unnecessary, or vice versa. What happens: someone delivers only the contract ("I already verify both providers") or only the integration ("I already cross the real seam"), thinking one covers the other. Why it happens: both give confidence, and it's tempting to keep one. How to detect it: ask yourself what bug would slip past you. Without integration, a bug of use that the contract didn't enumerate (the datetime that cancel can't subtract) passes. Without a contract, a divergence in a clause that the integration didn't exercise (the find_by_room of an empty room) passes. How to fix it: deliver both. The contract certifies each piece against the spec; the integration verifies the live collaboration. They're distinct layers of the same guarantee, and the capstone asks for both on purpose.

Doubling the seam you want to test. What happens: someone, out of the habit of writing unit tests, also doubles the repository in the integration test, leaving FakeBookingRepository everywhere. Why it happens: doubling everything is the reflex of unit testing, and it's done without thinking. How to detect it: if in your "integration" no real piece crosses the seam, it's not an integration —it's a unit test with another name—. How to fix it: the table's rule is exact: you double what's slow, non-deterministic, and external, but you keep real the seam you're testing. In this capstone that seam is the repository; doubling it empties the integration of meaning. Real the repository, doubled everything else.

Exercises

Exercise 1 — Fill in the why column. For each of BookingService's collaborators in the integration test, say whether it goes doubled or real and give the reason in one sentence: (a) the SqliteBookingRepository; (b) the StubPaymentGateway; (c) the FixedClock; (d) the SpyEmailSender.

See solution
  • (a) SqliteBookingRepository — real. It's the seam the integration tests. The whole point of the test is to see BookingService and the repository for real work together crossing the seam; doubling it would eliminate the only thing you want to verify.
  • (b) StubPaymentGateway — doubled. The payment is an external collaborator: charging for real would touch a card and a third-party service. It's not the seam under test, so it's doubled so the test is free, fast, and without real effects.
  • (c) FixedClock — doubled. The clock is non-deterministic: the real time changes on every run. cancel calculates the refund based on the notice before the start, so freezing the clock is what turns each anchor (6000, 3000, 0) into a fixed and repeatable datum of the test.
  • (d) SpyEmailSender — doubled. The email is external and with a side effect: sending a real email would fill inboxes and depend on the network. It's doubled; the spy records the sends without sending them, in case the test needs to verify that a notice was given.

The pattern you're applying is the golden rule: real the seam you test (the repository), doubled what's slow, non-deterministic, and external (the rest). Knowing how to say the reason for each cell is exactly what the capstone evaluates.

Exercise 2 — What bug each deliverable catches. Match each Reservo bug with the deliverable that would catch it, and explain why the other would let it pass: (a) SqliteBookingRepository.get of an absent id starts returning None; (b) SqliteBookingRepository.get returns the start as a str and cancel can't subtract it.

See solution
  • (a) The get that returns None is caught by the contract (deliverables 1 and 2). The clause "get of an absent id raises", run against the real one, goes red as soon as the provider stops raising: test_get_of_a_missing_id_raises[sqlite] fails with DID NOT RAISE KeyError. It's a divergence of behavior in an enumerated case, exactly what a contract covers. A happy-flow integration (bookcancelget with ids that do exist) might never exercise the absent id, and let it pass.
  • (b) The start that comes back as a str is caught by the integration (deliverable 3). cancel uses the start in a date subtraction, and a str isn't subtracted from a datetime: the flow blows up with a TypeError. The integration catches it through the use, without any assertion pointing at the start. An incomplete contract —that verifies status and price_cents but forgot to compare the start— would let it pass in green, because it never looked at the field that diverges.

The moral of the matching is the reason the capstone asks for both deliverables: the contract catches the divergences you enumerate in clauses; the integration catches the ones the real use triggers even if you didn't enumerate them. Each has a blind spot the other covers.

Exercise 3 — Choose the contract's seam. Reservo has two candidate seams for a contract: the BookingRepository and the PaymentGateway. The capstone chooses the repository. Give two reasons why the repository is the best choice for practicing contract testing, and a situation in which the gateway would deserve its own contract.

See solution

Two reasons to choose the repository:

  1. It's where the fake-vs-real divergence is richest. The SqliteBookingRepository serializes (datetime→text), has transactions (the commit), raises or not depending on whether it finds the row, and updates with ON CONFLICT. Each of those behaviors is an opportunity for the in-memory fake to diverge from the real one. The repository's contract, therefore, protects against concrete and varied bugs —exactly the ones the guide has been catching—.
  2. It has two real implementations that must coincide. The project already has a FakeBookingRepository (for fast unit tests) and a SqliteBookingRepository (for production). A contract is the precise tool for keeping those two honest with each other. Where there's a double and a real one that are actually used, there's a concrete need for a contract.

When the gateway would deserve its own contract: if PaymentGateway had, like the repository, a double used in the tests (StubPaymentGateway) and a real implementation against a concrete payment service, and the consumer depended on subtle behaviors of that service —for example, "a rejected charge raises PaymentDeclined, doesn't return None", or "a refund greater than the original charge raises"—. As soon as the stub and the real one could diverge in a behavior BookingService depends on, the gateway calls for its own contract, written from the consumer's needs just like the repository's. The technique is the same; what changes is which seam runs the risk of diverging in your system.

Summary and next step

In this lesson you saw the complete capstone before building it. You understood that it weaves the seven modules into a single process over Reservo, with three deliverables that are three layers of the same guarantee: the consumer-driven contract (deliverable 1), its verification from both sides against the fake and the real (deliverable 2), and the isolated end-to-end integration bookgetcancel (deliverable 3). You set the criterion that governs everything with the table of what to double and what to test real —real the seam you test, the repository; doubled what's slow, non-deterministic, and external, the payment, the clock, and the email— and you learned that what's evaluated is the method, not the quantity. And with the final walkthrough you saw that a capstone doesn't re-calculate pieces: it verifies that the system, assembled, works. You closed with a glimpse of the finished deliverable: ten greens, the contract and the integration running together.

Before moving on you should be able to: name the three deliverables and what bug each catches; apply the rule of what to double and what to keep real to each of BookingService's collaborators, with the reason; and explain why the repository, and not the gateway, is the seam this capstone chooses for the contract.

With the map clear, the construction begins. In lesson 2 you write the first deliverable: the BookingRepository's consumer-driven contract. You're going to derive each of the four clauses from a concrete need of BookingService —not from the provider's catalog— and assemble the battery with the parametrized fixture that will run it, in the following lessons, against the fake and against the real one. The complete process begins by writing the walkthrough's checklist.

Resources

  • pytest documentation — Getting Started — the official entry point to pytest, the tool with which we run and cite every output of the capstone; useful for re-confirming your environment (Python 3.14, pytest 9.1.1) before starting the delivery.
  • sqlite3 — DB-API for SQLite (Python documentation) — the reference for the real resource the contract certifies and the integration crosses; it's the walkthrough's "real system", without external dependencies.
  • docs.pact.io — Consumer-driven contracts — the industry reference for the pattern you build by hand in deliverable 1: the consumer defines the contract and the provider is verified against it.
  • testing-backend-applications-guide — where the path continues when the seam stops being in-process and becomes a real web app with a framework and end-to-end HTTP; the natural destination after closing this guide.