Testing Basics
10 examples to get you started with Testing - 7 basic and 3 intermediate.
Prerequisites
- Node.js 24.18.0 with built-in
node:testandnode:assert. - TypeScript service with
createApp()export pattern. - Optional:
npm install -D tsx supertest @types/supertestfor HTTP integration tests.
Basic Examples
1. Testing Pyramid for HTTP APIs
Balance fast unit tests with fewer integration and E2E tests.
/ E2E \ few - critical user journeys
/ contract \ consumer-provider agreements
/ integration \ HTTP + DB with Testcontainers
/ unit tests \ many - pure logic, handlers mocked
- Most tests should be unit-level (milliseconds each).
- Integration tests prove wiring; E2E proves deployed environment.
- Contract tests matter when multiple services evolve APIs independently.
2. node:test Smoke Test
Zero-dependency test runner built into Node 24.
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { calculateTax } from "../src/billing/tax.js";
describe("calculateTax", () => {
it("applies rate to subtotal", () => {
assert.equal(calculateTax(100, 0.2), 20);
});
});node --import tsx --test test/tax.test.ts- No Jest needed for pure functions.
node:assert/strictuses === semantics.
Related: node:test & node:assert - full runner guide
3. Export createApp for HTTP Tests
Separate server factory from listen for Supertest.
// src/app.ts
import express from "express";
export function createApp() {
const app = express();
app.get("/health", (_req, res) => res.json({ ok: true }));
return app;
}- Tests import
createApp()without binding a port. - Production
server.tscallscreateApp().listen(...). - Pattern works for Express 5 and Fastify with
app.ready().
Related: Supertest & HTTP Integration - in-process HTTP
4. Arrange-Act-Assert Structure
Readable tests document expected behavior.
it("returns 404 for unknown invoice", async () => {
// Arrange
const app = createApp();
// Act
const res = await request(app).get("/invoices/missing");
// Assert
assert.equal(res.status, 404);
});- One logical assertion focus per test when possible.
- Name tests as behavior:
returns 404 when .... - Avoid testing private functions; test public HTTP or module API.
5. Isolate Tests with beforeEach
Reset mocks and state between cases.
import { beforeEach, describe, it } from "node:test";
let cache: Map<string, string>;
beforeEach(() => {
cache = new Map();
});- Prevents order-dependent failures.
- Use in-memory stores in unit tests; real DB only in integration suite.
6. npm test Script
Single entry for local and CI.
{
"scripts": {
"test": "node --import tsx --test",
"test:unit": "node --import tsx --test test/unit",
"test:integration": "node --import tsx --test test/integration"
}
}- Split suites when integration needs Docker services.
- CI runs both; developers run unit loop during TDD.
7. Test Environment Variables
Use .env.test or inline defaults for test-only config.
process.env.DATABASE_URL ??= "postgres://localhost:5432/billing_test";
process.env.LOG_LEVEL = "silent";- Never point tests at production databases.
LOG_LEVEL=silentkeeps test output readable.
Intermediate Examples
8. Integration Test with Real Postgres
Testcontainers spins ephemeral DB in CI.
// test/integration/invoices.test.ts - conceptual placement
// See Testcontainers page for full setup- Run in separate job with Docker available.
- One schema migrate per suite; truncate tables between tests.
Related: Testcontainers - Postgres/Redis in CI
9. Contract Test Between Services
Consumer defines expected provider response shape.
// Consumer pact test - provider must match published contract
// See Contract & Pact page- Catches breaking API changes before deploy.
- Run on provider CI verifying all consumer pacts.
Related: Contract & Pact Tests - microservice boundaries
10. Load Test Before Launch
Baseline latency and error rate under expected RPS.
k6 run scripts/load/smoke.js- Not a replacement for unit tests; validates SLO assumptions.
- Run against staging with production-like data volume.
Related: Load Testing with k6/Artillery - baseline RPS
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.