Skip to main content

Testing and Deploying Web Apps

Testing pyramid​

LevelScopeToolsShare
UnitOne function/componentVitest, JestMost
IntegrationSeveral modules or an API routeSupertest, Testing LibrarySome
End-to-endThe whole app in a browserPlaywright, CypressFew, critical flows

Unit test example​

import { describe, it, expect } from "vitest";
import { total } from "./cart";

describe("total", () => {
it("adds item prices", () => {
expect(total([{ price: 10 }, { price: 5 }])).toBe(15);
});

it("returns 0 for an empty cart", () => {
expect(total([])).toBe(0);
});
});

API test example​

import request from "supertest";
import app from "./app";

it("rejects an empty note", async () => {
const res = await request(app).post("/api/notes").send({ text: "" });
expect(res.status).toBe(422);
});

Continuous integration​

Run lint, tests and build on every push.

name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build

Docker​

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "src/server.js"]

Deployment options​

OptionGood for
Static hosting / CDN (Vercel, Netlify, GitHub Pages)Front-end and static sites
Platform-as-a-service (Render, Railway, Fly.io)Node/Python APIs with managed databases
Containers on a cloud providerFull control and scaling

Release checklist​

  • Environment variables set in the host, not in the repo
  • Database migrations applied
  • Health-check endpoint (/health) and logging
  • HTTPS and a custom domain
  • Rollback plan (previous version redeployable)