How do you test a REST API (unit, integration, contract)?
Learn how to test a REST API in layers: fast unit tests, integration tests against a real database, and contract tests that prevent breaking changes.
Expected Interview Answer
Test a REST API in layers: unit tests for isolated logic with dependencies mocked, integration tests that exercise real routes plus the database and other services, and contract tests that verify the request/response shape agreed between provider and consumer.
Unit tests target handlers, validators, and services in isolation, mocking I/O so they run fast and pinpoint failures. Integration tests spin up the app and a real (or containerized) database to confirm routes, status codes, serialization, and persistence work end to end. Contract tests (e.g. Pact or schema-based checks against an OpenAPI spec) ensure the API's shape does not drift from what consumers expect, catching breaking changes before deployment. Together they give a testing pyramid: many fast unit tests, fewer integration tests, and targeted contract tests at the boundaries.
- Fast feedback from isolated unit tests
- Confidence that routes and the database work together
- Prevents breaking API changes for consumers
- Documents expected behavior as executable tests
- Enables safe refactoring and continuous delivery
AI Mentor Explanation
It is like preparing a team at three levels: net practice drills each batter's technique in isolation (unit tests), a full practice match tests the eleven playing together with a real pitch and ball (integration tests), and the match-rules agreement with officials fixes what both sides expect (contract tests) so nobody is surprised on game day.
Step-by-Step Explanation
Step 1
Write unit tests
Test handlers, validators, and services in isolation with mocked databases and external calls for fast, focused feedback.
Step 2
Add integration tests
Boot the app against a real or containerized database and hit routes with a client to verify status codes, bodies, and persistence.
Step 3
Verify contracts
Use consumer-driven contract tests (Pact) or validate responses against an OpenAPI schema so the API shape cannot drift.
Step 4
Cover error and edge cases
Assert validation errors, auth failures, 404s, and pagination behavior, not just happy paths.
Step 5
Run in CI
Execute the pyramid on every push, using ephemeral databases and seeded fixtures for repeatable, isolated runs.
What Interviewer Expects
- Clear distinction between unit, integration, and contract tests
- Awareness of the testing pyramid and its trade-offs
- Use of test clients like supertest against real routes
- Knowledge of consumer-driven contracts or schema validation
- Testing error paths, auth, and edge cases, not just success
Common Mistakes
- Calling everything an integration test with no isolation
- Hitting live third-party services instead of mocking or stubbing
- Only testing happy paths and ignoring error responses
- No contract tests, so breaking changes reach consumers
- Flaky tests from shared state and unseeded databases
Best Answer (HR Friendly)
“You test the API in layers: small tests check individual pieces of logic, bigger tests run the real routes against a database to confirm they work together, and contract tests make sure the API keeps returning what other teams and apps depend on.”
Code Example
const request = require('supertest')
const app = require('../app')
describe('POST /users', () => {
it('creates a user and returns 201', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Ada', email: '[email protected]' })
expect(res.status).toBe(201)
expect(res.body).toMatchObject({ name: 'Ada' })
expect(res.headers.location).toMatch(/\/users\/\w+/)
})
it('rejects invalid email with 400', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Ada', email: 'not-an-email' })
expect(res.status).toBe(400)
})
})Follow-up Questions
- What is consumer-driven contract testing and when is it worth it?
- How do you isolate the database between integration tests?
- How would you validate responses against an OpenAPI spec?
- How do you avoid flaky API tests in CI?
- Where do end-to-end tests fit in the testing pyramid?
MCQ Practice
1. Which test type verifies the request/response shape agreed between provider and consumer?
Contract tests ensure the API's shape matches what consumers expect, catching breaking changes before they ship.
2. What best describes a unit test for an API?
Unit tests isolate a function or handler and mock I/O so they run fast and pinpoint failures.
3. In the testing pyramid, which layer should have the most tests?
The pyramid favors many fast unit tests, fewer integration tests, and even fewer slow end-to-end tests.
Flash Cards
What does a unit test cover for an API? — Isolated logic (handlers, validators, services) with databases and external calls mocked.
What does an integration test verify? — Real routes plus database and services working together, checking status codes, bodies, and persistence.
What is a contract test? — A test that the API's request/response shape matches what consumers expect, e.g. via Pact or OpenAPI schema.
What is the testing pyramid? — Many unit tests, fewer integration tests, and a small number of slow end-to-end tests.
Continue Learning
Related Interview Questions
What is contract testing and how does consumer-driven contract testing work?
medium
How do you document a REST API with OpenAPI / Swagger?
easy
Which REST API changes are breaking, and how do you evolve a contract without a new version?
medium
What is the difference between Django's TestCase and TransactionTestCase, and how do you keep a test suite fast and isolated?
hard