Testing and documenting the API
An API is a promise to the apps that call it. This lesson shows how to prove the promise with tests that run against a real PostgreSQL, and how to write it down as an OpenAPI document that Lagoon Digital can build the Partner app against without guessing.
By the end you can
- Decide which checks belong in a unit test with fakes and which need an integration test over HTTP
- Run integration tests against a real PostgreSQL on your laptop and in GitHub Actions, with clean state for every test
- Turn the course's rules into tests: one error shape, validation, 404 for another shop's order, one order per Idempotency-Key
- Generate an OpenAPI document with @nestjs/swagger, commit it, and block breaking changes in CI
- Retire an old endpoint with a changelog, a Deprecation header and a Sunset date
The everyday picture
Think of a bakery that supplies fifty cafés. The baker tastes each new dough on its own before baking: is it sweet enough, is the salt right? That check is quick, and you can do it a hundred times a day.
But a dough that tastes perfect can still fail in the real oven. The oven runs hot at the back, the tray sticks, two batches do not fit at once. A cardboard oven would never show these problems. So once a day the baker bakes a real batch in the real oven and looks at what comes out.
The cafés order from a printed menu. If the baker quietly drops the almond cake from the menu, the cafés that sell it every morning find out at 6 am, with customers waiting. A good bakery tells them weeks before, and says the exact date the cake goes away.
An API has all three. Unit tests taste the dough. Integration tests bake in the real oven, which here means the real database. The OpenAPI document is the menu, and the change policy is how you warn the cafés.
What each kind of test can prove
The testing pyramid from Errors, debugging and testing applies to an API too: many fast tests at the bottom, fewer slow ones on top. For a NestJS service the two layers that matter most look like this.
A unit test builds one service with its dependencies replaced by fakes. NestJS gives you Test.createTestingModule(), which builds a small dependency injection container just for the test (the container itself is explained in Your first NestJS service). You list the real service you want to test and swap each dependency with overrideProvider(X).useValue(fake). The test calls a method and checks what comes back. It runs in a few milliseconds and points straight at the broken rule: the price total, the credit limit check, the decision about which status an order may move to.
An integration test boots the whole application and calls it over HTTP, the same way the Partner app does. The tool for this is supertest. The request goes through the real guards, the real ValidationPipe, the real exception filter, the real Prisma client and a real PostgreSQL database. Each test takes tens or hundreds of milliseconds, and a run of 50 might take 20 to 40 seconds, which is still fine for every pull request.
Why not mock Prisma and keep everything fast? Because a mock of the database only returns what you told it to return. Many of the bugs that hurt partner shops live in the database itself:
- A unique constraint rejects the second insert of an Idempotency-Key (Background jobs and idempotency), and Prisma throws error code
P2002. If nothing maps that error, the shop gets a 500. - A foreign key or CHECK constraint rejects a row the service thought was fine.
- A transaction that should roll back the order when an order line fails leaves half an order behind (Data with PostgreSQL and Prisma).
- A
whereclause forgets the shop, so one shop can read another shop's orders (Who are you and what may you do?). The mock returns the order you set up, so the test passes. - A migration is missing, or a raw SQL query has a typo. The mock never runs SQL at all.
So the rule for this course: services with real logic get unit tests with fakes; anything that touches the database is tested against a real one. Mock the things you do not own and cannot run in CI, such as an SMS gateway or a payment provider, and never the database you ship with.
Quick check
Which bug can a unit test with a mocked Prisma client NOT find?
The unique constraint lives in PostgreSQL, so only a real database raises P2002. A mock returns whatever you programmed, and you would only program that error if you already knew about the bug. The total and the status rule are pure logic, which a unit test checks well.