Testing
Jest and Supertest setup for API testing.
Every generated project comes with Jest, Supertest, and a set of starter tests. The test suite runs against your Express app directly, without starting the HTTP server.
Running tests
npm testpnpm testyarn testConfiguration
The Jest config lives at jest.config.js in the project root:
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
testMatch: ["**/tests/**/*.test.ts"],
moduleNameMapper: {
"^(\\.\\.?/.*)\\.js$": "$1",
},
};The moduleNameMapper is important. TypeScript source uses .js extensions in imports (for ESM compatibility), but Jest needs to resolve those to the actual .ts files. This mapping strips the .js extension so ts-jest can find them.
Generated tests
tests/app.test.ts
Covers the core endpoints every project has:
import request from "supertest";
import app from "../src/app.js";
describe("App", () => {
it("should return welcome message on GET /", async () => {
const res = await request(app).get("/");
expect(res.status).toBe(200);
expect(res.body).toHaveProperty("message");
expect(res.body).toHaveProperty("version");
});
it("should return health status on GET /health", async () => {
const res = await request(app).get("/health");
expect(res.status).toBe(200);
expect(res.body).toHaveProperty("status", "ok");
});
it("should return 404 for unknown routes", async () => {
const res = await request(app).get("/unknown-route");
expect(res.status).toBe(404);
});
});tests/example.test.ts
Generated when no database is selected. Tests the full CRUD lifecycle against the in-memory example route:
import request from "supertest";
import app from "../src/app.js";
describe("Example API", () => {
let createdId: number;
it("should create an example on POST /api/examples", async () => {
const res = await request(app)
.post("/api/examples")
.send({ name: "Test", description: "A test example" });
expect(res.status).toBe(201);
expect(res.body).toHaveProperty("id");
createdId = res.body.id;
});
it("should return 400 for invalid POST body", async () => {
const res = await request(app)
.post("/api/examples")
.send({ name: "" });
expect(res.status).toBe(400);
});
it("should delete an example on DELETE /api/examples/:id", async () => {
const res = await request(app).delete(`/api/examples/${createdId}`);
expect(res.status).toBe(204);
});
});When a database is selected, this file is not generated because the CRUD route depends on a live database connection.
Writing your own tests
Tests go in the tests/ directory and must match *.test.ts. Here's the pattern:
import request from "supertest";
import app from "../src/app.js";
describe("Users API", () => {
it("should list users", async () => {
const res = await request(app).get("/api/users");
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
it("should validate request body", async () => {
const res = await request(app)
.post("/api/users")
.send({ email: "not-an-email" });
expect(res.status).toBe(400);
expect(res.body).toHaveProperty("error");
});
});Key things to note:
- Import
app, notserver. The app is the Express instance withoutlisten(), so Supertest can bind to it directly. - Import paths use
.jsextensions (../src/app.js). ThemoduleNameMapperin Jest config handles resolving these to.tsfiles. - Rate limiting is skipped when
NODE_ENV=test, so it won't interfere with your tests.
Testing with a database
When using a database ORM, you'll need a test database. The simplest approach is a separate .env.test file:
DATABASE_URL="postgresql://user:password@localhost:5432/mydb_test"
NODE_ENV=testLoad it before running tests by updating your test script:
{
"scripts": {
"test": "NODE_ENV=test jest"
}
}For SQLite, you can use an in-memory database or a separate file:
DATABASE_URL="./test.db"
NODE_ENV=test