1. Courses
  2. Developer Foundations
  3. Building APIs with Node.js
  4. Stop 6

40 minIntermediate

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 . 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 where clause 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.

Unit test (ms) Integration (s) Jest test quote() OrdersService pricesFor() fake prices Catches: totals, rules, branches Misses: SQL and constraints supertest HTTP + token Nest pipeline valid DTO OrdersService real SQL PostgreSQL 16 Catches: 400, 404, unique, FK, txn Slower, so fewer of them
The unit test stops at a fake, so it is fast and precise about business rules. The integration test goes through guards, pipes, the exception filter and Prisma into a real PostgreSQL, so it also catches status codes, error shapes, constraints and transactions.

Quick check

Which bug can a unit test with a mocked Prisma client NOT find?

Loading the rest of the lesson…