API testing verifies that your HTTP endpoints behave correctly: right status codes, correct response shapes, and proper handling of authentication and errors. This lesson covers strategies that apply beyond any single framework.
What to Verify for Every Endpoint
A thorough API test checks more than "it returns 200". At minimum, verify the status code, the response body's shape (using .toMatchObject() or .toHaveProperty() rather than a fragile full-equality check), and any relevant headers (like Content-Type).
Beyond the happy path, test authentication requirements, input validation errors, and not-found scenarios — these are exactly the cases most likely to have bugs in real production APIs.
Checking status, content type, and a partial body shape together gives solid coverage without an overly brittle exact-match assertion.
Testing Authentication Requirements
test('returns 401 without a valid token', async () => {
const response = await request(app).get('/profile');
expect(response.status).toBe(401);
});
test('returns 200 with a valid token', async () => {
const response = await request(app)
.get('/profile')
.set('Authorization', `Bearer ${validTestToken}`);
expect(response.status).toBe(200);
});
Always test both an unauthenticated request and an authenticated one for protected routes.
Use a test-specific token (signed with a test secret, or a mocked auth middleware) rather than a real production credential.
Test expired or malformed tokens separately from missing tokens — they often hit different code paths.
Avoid hardcoding real secrets in test files; use environment variables or test fixtures.
API Testing Checklist
What a thorough API test suite should cover per endpoint.
Scenario
Expected Behavior
Valid request
Correct status code and response shape
Missing required field
400 with a descriptive error message
Unauthenticated request
401 for protected routes
Insufficient permissions
403 for authenticated but unauthorized users
Resource not found
404 with a clear error
Unexpected server error
500 without leaking internal details
Response content type
Matches expected format (e.g. application/json)
Validating Response Schemas
For APIs with many fields or nested structures, consider a schema validation library (like Zod or Ajv with JSON Schema) to assert the entire response shape at once, rather than manually listing every field with .toHaveProperty().
For list endpoints, test that pagination parameters (?page=2&limit=10) and filters (?status=active) actually affect the returned data, not just that the endpoint returns *something*.
test('returns only active users when filtered', async () => {
const response = await request(app).get('/users?status=active');
expect(response.body.every((u) => u.status === 'active')).toBe(true);
});
Common Mistakes
Only testing the happy path and skipping validation, auth, and not-found scenarios.
Using full .toEqual() on a response body that includes volatile fields like timestamps or IDs.
Hardcoding real credentials or secrets directly in API test files.
Not verifying that filters or pagination parameters actually change the returned data.
Key Takeaways
A thorough API test checks status code, response shape, and relevant headers together.
Test authentication and authorization for every protected endpoint, not just the happy path.
Schema validation libraries can simplify asserting on large or complex response shapes.
Always verify that filters and pagination parameters genuinely affect the response.
Pro Tip
For every new endpoint, write down the list of scenarios (valid, missing field, unauthenticated, not found, server error) before writing any test code — treating it like a checklist catches gaps that ad hoc testing tends to miss.
You now know solid API testing strategies. Next, learn how to test code that talks to a database.