2026-04-05

This Is a Test: A Practical Guide to Fast, Reliable Testing

If you have ever merged a quick fix and crossed your fingers, this post is for you. Below is a practical, copy paste friendly path to get tests in place fast, keep them reliable, and wire them into CI.

What you will get:

  • A minimal testing stack (JS and Python examples)
  • Project layout that scales
  • Patterns for fixtures, factories, and deterministic tests
  • API testing examples
  • CI setup that runs in minutes

1) Pick a minimal stack

Choose the smallest tool that solves the problem now and can grow later.

  • Node: Jest for unit and integration, Supertest for HTTP, Playwright for browser
  • Python: pytest for unit and integration, httpx or requests for HTTP, Playwright for browser
  • Databases: SQLite in memory for speed when possible; Postgres with Testcontainers or a local service when you need parity

Keep it boring, fast, and easy to run locally and in CI.


2) Suggested project layout

project/
  src/
    index.js
    api/
      server.js
    lib/
      math.js
  tests/
    unit/
      math.test.js
    integration/
      api.test.js
    fixtures/
      factories.js
  py/
    app/
      __init__.py
      api.py
      math.py
    tests/
      test_math.py
      test_api.py
      factories.py
  • Keep tests close but separate
  • Name tests predictably: thing.test.js or test_thing.py
  • A factories module avoids ad hoc data construction

3) First test in JavaScript (Jest)

Install:

npm i -D jest
npx jest --init

Code under test:

// src/lib/math.js
function sum(a, b) {
  return a + b
}
module.exports = { sum }

Test:

// tests/unit/math.test.js
const { sum } = require('../../src/lib/math')

describe('sum', () => {
  it('adds two numbers', () => {
    expect(sum(2, 3)).toBe(5)
  })
})

Run:

npx jest

4) First test in Python (pytest)

Install:

pip install pytest

Code under test:

# py/app/math.py
def sum(a, b):
    return a + b

Test:

# py/tests/test_math.py
from app.math import sum

def test_sum_adds_two_numbers():
    assert sum(2, 3) == 5

Run:

pytest -q

5) Test an API fast

JavaScript with Express and Supertest:

npm i express supertest
// src/api/server.js
const express = require('express')
const app = express()
app.get('/health', (req, res) => res.status(200).json({ ok: true }))
module.exports = app
// tests/integration/api.test.js
const request = require('supertest')
const app = require('../../src/api/server')

describe('GET /health', () => {
  it('returns ok', async () => {
    const res = await request(app).get('/health')
    expect(res.status).toBe(200)
    expect(res.body).toEqual({ ok: true })
  })
})

Python with FastAPI and httpx:

pip install fastapi uvicorn httpx pytest
# py/app/api.py
from fastapi import FastAPI

app = FastAPI()

@app.get('/health')
def health():
    return {'ok': True}
# py/tests/test_api.py
from httpx import AsyncClient
from app.api import app
import pytest

@pytest.mark.asyncio
async def test_health():
    async with AsyncClient(app=app, base_url='http://test') as ac:
        res = await ac.get('/health')
    assert res.status_code == 200
    assert res.json() == {'ok': True}

6) Fixtures and factories

Avoid brittle hand made data. Use factories.

JavaScript example:

// tests/fixtures/factories.js
let idCounter = 1
const userFactory = (overrides = {}) => ({
  id: idCounter++,
  email: `user${Date.now()}_${Math.random().toString(36).slice(2)}@test.dev`,
  name: 'Test User',
  ...overrides,
})
module.exports = { userFactory }

Python example:

# py/tests/factories.py
import time, random

def user_factory(**overrides):
    base = {
        'id': int(time.time() * 1000),
        'email': f'user{int(time.time()*1000)}_{random.randint(0,9999)}@test.dev',
        'name': 'Test User',
    }
    base.update(overrides)
    return base

Tip: if randomness leaks into assertions, your tests will flake. Inject clocks and id generators so you can replace them in tests.


7) Make tests deterministic

  • Time
    • JavaScript: jest.useFakeTimers(); advanceTimersByTime
    • Python: freezegun to freeze time
  • Randomness
    • Inject a rng function and replace with a fixed seed in tests
  • Network
    • Mock external HTTP with nock (JS) or responses (Python)
  • Concurrency
    • Avoid sleeps. Wait on signals or events. Use timeouts with clear failure messages

JavaScript fake time example:

jest.useFakeTimers()
const fn = jest.fn()
setTimeout(fn, 1000)
jest.advanceTimersByTime(1000)
expect(fn).toHaveBeenCalled()

Python freezegun example:

pip install freezegun
from freezegun import freeze_time
from datetime import datetime

@freeze_time('2024-01-01 00:00:00')
def test_time_is_frozen():
    assert datetime.now().year == 2024

8) Fast feedback: run the right tests

  • Use watch mode locally (jest --watch, pytest -f)
  • Tag slow tests and skip them by default
    • Jest: use a naming or tag convention
    • pytest: use markers like @pytest.mark.slow and run with -m 'not slow'
  • Parallelize: jest runs in parallel by default; pytest with -n auto via pytest xdist
pip install pytest-xdist
pytest -n auto

9) Data and databases

  • Prefer pure functions where possible
  • For persistence tests:
    • Use SQLite in memory for speed when it matches your SQL dialect
    • Else, spin up a real Postgres using Testcontainers or docker compose
    • Wrap tests in transactions and roll back to keep tests isolated

Minimal docker compose for Postgres:

version: '3.9'
services:
  db:
    image: postgres:16
    environment:
      - POSTGRES_PASSWORD=pass
      - POSTGRES_USER=user
      - POSTGRES_DB=app
    ports:
      - 5432:5432

10) CI in minutes (GitHub Actions)

name: ci
on:
  pull_request:
  push:
    branches: [ main ]
jobs:
  test-js:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx jest --ci --reporters=default --coverage
  test-py:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt || true
      - run: pip install pytest
      - run: pytest -q

Speed tips:

  • Cache dependencies with setup actions cache inputs
  • Split jobs by area (unit, integration)
  • Run affected tests only using path filters or a test selection tool

11) Flaky test checklist

  • Are you waiting for a timer or a fixed sleep? Replace with an event or poll with timeout
  • Does the test depend on real time, rand, or global state? Inject and control them
  • Is there shared data across tests? Reset between tests
  • Do tests run reliably on a slow CI runner? Add generous timeouts and clear diagnostics
  • External services: mock them or run local emulators

12) Day one checklist

  • Unit tests run locally with one command
  • A couple of integration tests cover the critical path
  • Deterministic time and id generation in tests
  • Factories replace ad hoc data
  • CI runs tests on every PR in under 10 minutes

Ship it. The best test suite is the one you actually run every time.