|
| 1 | +--- |
| 2 | +sidebar_label: 202. Contract Testing |
| 3 | +--- |
| 4 | + |
| 5 | +# Contract Testing |
| 6 | + |
| 7 | +## Setup |
| 8 | + |
| 9 | +```sh |
| 10 | +pnpm cook start 202-contract-testing |
| 11 | +``` |
| 12 | + |
| 13 | +To make sure that the fake and the real implementation behave the same way, you will implement a **contract test**: a shared suite of expectations that will run against **every** implementation. In this project, `verifyRecipeRepositoryContract` is already wired up for: |
| 14 | + |
| 15 | +- `RecipeRepositoryFake` in `recipe-repository.fake.spec.ts` (in-memory, fast) |
| 16 | +- the real HTTP `RecipeRepository` in `recipe-repository.wide.spec.ts` (slow, hits the network) |
| 17 | + |
| 18 | +You must **implement the contract**. |
| 19 | + |
| 20 | +## π― Goal #1: `searchRecipes()` returns all recipes |
| 21 | + |
| 22 | +When called with no filter, the repository should return recipes β including at least one named **Burger** (present in both the fake seed data and the remote API). |
| 23 | + |
| 24 | +### π Steps |
| 25 | + |
| 26 | +#### 1. Run tests in watch mode. |
| 27 | + |
| 28 | +```sh |
| 29 | +pnpm test |
| 30 | +``` |
| 31 | + |
| 32 | +#### 2. Go to `src/app/recipe/recipe-repository.contract.ts`. |
| 33 | + |
| 34 | +#### 3. Implement the **returns all recipes** test. |
| 35 | + |
| 36 | +- Create the recipe repository using the `createRecipeRepositoryFake` factory function. |
| 37 | +- Call `searchRecipes()` with no arguments. |
| 38 | +- Assert the result **contains** a recipe whose `name` is `Burger` (See [Partial object matching](#-partial-object-matching)). |
| 39 | + |
| 40 | +#### 4. Turn on the test by replacing `it.todo` with `it`. |
| 41 | + |
| 42 | +#### 5. Make sure the test passes for both the fake and the real implementation. |
| 43 | + |
| 44 | +## π― Goal #2: `searchRecipes` filters by keywords |
| 45 | + |
| 46 | +Calling `searchRecipes({ keywords: 'burg' })` should return only recipes whose names match that filter β no unrelated recipes in the result. |
| 47 | + |
| 48 | +### π Steps |
| 49 | + |
| 50 | +#### 1. Go to `src/app/recipe/recipe-repository.contract.ts`. |
| 51 | + |
| 52 | +#### 2. Implement the **filters recipes containg "burg" keywords** test. |
| 53 | + |
| 54 | +- Call `searchRecipes({ keywords: 'burg' })`. |
| 55 | +- Split results into recipes whose `name` includes `Burger` and recipes that do not. |
| 56 | +- Assert there is **at least one** burger recipe, and **no** other recipes. |
| 57 | + |
| 58 | +#### 3. Replace `it.todo` with `it`. |
| 59 | + |
| 60 | +#### 4. Make sure the test passes for both the fake and the real implementation. |
| 61 | + |
| 62 | +## π― Goal #3: No match returns an empty array |
| 63 | + |
| 64 | +### π Steps |
| 65 | + |
| 66 | +#### 1. Go to `src/app/recipe/recipe-repository.contract.ts`. |
| 67 | + |
| 68 | +#### 2. Implement the **returns an empty array when no recipes are found** test. |
| 69 | + |
| 70 | +- Call `searchRecipes` with keywords that cannot match any seeded or API recipe (e.g. `pizza with salmon and pineapple`). |
| 71 | +- Assert the result has length `0`. |
| 72 | + |
| 73 | +#### 3. Replace `it.todo` with `it`. |
| 74 | + |
| 75 | +#### 4. Make sure the test passes for both the fake and the real implementation. |
| 76 | + |
| 77 | +## π Appendices |
| 78 | + |
| 79 | +### π Partial object matching |
| 80 | + |
| 81 | +Use `toContainEqual` with `expect.objectContaining` when you only care about a subset of fields: |
| 82 | + |
| 83 | +```ts |
| 84 | +expect(recipes).toContainEqual(expect.objectContaining({ name: 'Burger' })); |
| 85 | +``` |
| 86 | + |
| 87 | +This avoids brittle full-object equality when recipes carry ids, ingredients, etc. |
0 commit comments