Testing REST APIs (Jest + Supertest)
Automated tests are your safety net when making changes. Jest is the most popular JavaScript testing framework and Supertest lets you fire real HTTP requests against your Express app in memory, without needing a running server.
1 Setup
Terminal — Install Testing Dependencies
npm install --save-dev jest supertest
# package.json scripts
"scripts": {
"test": "jest --detectOpenHandles",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
2 Writing API Integration Tests
JavaScript — products.test.js
const request = require("supertest");
const app = require("../src/app"); // your Express app
describe("Products API", () => {
describe("GET /api/v1/products", () => {
it("returns 200 with products array", async () => {
const res = await request(app).get("/api/v1/products");
expect(res.statusCode).toBe(200);
expect(res.body.success).toBe(true);
expect(Array.isArray(res.body.data)).toBe(true);
expect(res.body.meta).toHaveProperty("total");
});
});
describe("POST /api/v1/products", () => {
it("creates a new product with valid data", async () => {
const res = await request(app)
.post("/api/v1/products")
.set("Authorization", "Bearer " + validToken)
.send({ name: "Test Product", price: 1000, category: "electronics" });
expect(res.statusCode).toBe(201);
expect(res.body.data.name).toBe("Test Product");
expect(res.body.data.id).toBeDefined();
});
it("returns 422 when required fields are missing", async () => {
const res = await request(app)
.post("/api/v1/products")
.set("Authorization", "Bearer " + validToken)
.send({ name: "Incomplete" }); // missing price and category
expect(res.statusCode).toBe(422);
expect(res.body.error.code).toBe("VALIDATION_ERROR");
expect(res.body.error.fields).toHaveProperty("price");
});
it("returns 401 without a token", async () => {
const res = await request(app)
.post("/api/v1/products")
.send({ name: "Test", price: 100, category: "books" });
expect(res.statusCode).toBe(401);
});
});
describe("DELETE /api/v1/products/:id", () => {
it("deletes an existing product and returns 204", async () => {
const create = await request(app)
.post("/api/v1/products")
.set("Authorization", "Bearer " + adminToken)
.send({ name: "To Delete", price: 500, category: "books" });
const res = await request(app)
.delete("/api/v1/products/" + create.body.data.id)
.set("Authorization", "Bearer " + adminToken);
expect(res.statusCode).toBe(204);
});
});
});
3 Code Challenge
Challenge: Write a full test suite for a
POST /auth/login endpoint. Test: successful login returns 200 with a token, wrong password returns 401, missing fields return 422, and a non-existent user returns 401.