All topics
Frontend · Learning hub

Testing notes for developers

Master Testing with a curated set of 2 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Frontend notes
Testing

Testing Fundamentals & TDD

Testing Fundamentals & TDD Test Pyramid /\ / \ E2E Tests (few, slow, brittle) /----\ → Playwright, Cypress / \ → full user flows, critical paths /--------\ / \

Testing Fundamentals & TDD

Test Pyramid

          /\
         /  \         E2E Tests (few, slow, brittle)
        /----\        → Playwright, Cypress
       /      \       → full user flows, critical paths
      /--------\
     /          \     Integration Tests (some)
    /------------\    → API routes, DB queries, service layer
   /              \   → real DB or test DB, not mocked
  /----------------\
 /                  \ Unit Tests (many, fast, isolated)
/--------------------\ → pure functions, utils, business logic
                        → heavily mocked dependencies

Rule of thumb: 70% unit, 20% integration, 10% E2E
Avoid: testing implementation details (HOW) — test behaviour (WHAT)

Test-Driven Development (TDD)

  • Red — write a failing test for the desired behaviour

  • Green — write the minimum code to make the test pass

  • Refactor — clean up code without breaking tests

Writing Good Tests (FIRST / AAA)

// AAA pattern: Arrange, Act, Assert
describe('calculateTotal', () => {
  it('applies discount when order exceeds threshold', () => {
    // Arrange
    const items = [{ price: 60 }, { price: 50 }];
    const discountThreshold = 100;
    const discountRate = 0.1;

    // Act
    const total = calculateTotal(items, discountThreshold, discountRate);

    // Assert
    expect(total).toBe(99); // 110 - 10%
  });

  it('returns full price when under threshold', () => {
    const items = [{ price: 40 }, { price: 30 }];
    expect(calculateTotal(items, 100, 0.1)).toBe(70);
  });

  it('throws when items array is empty', () => {
    expect(() => calculateTotal([], 100, 0.1)).toThrow('No items');
  });
});

// FIRST principles:
// Fast       — unit tests should run in milliseconds
// Independent — tests don't share state or order-depend
// Repeatable — same result every run (no random, no time)
// Self-checking — pass/fail without manual inspection
// Timely      — written before or with the code

// Naming: "it should <do X> when <condition>"
// Avoid: "test1", "works correctly", "success case"

Integration & E2E Testing

// Integration test — API route with real DB (test DB)
import request from 'supertest';
import { app } from '../app';
import { db } from '../db';

beforeAll(async () => { await db.migrate.latest(); });
afterAll(async () => { await db.destroy(); });
afterEach(async () => { await db('users').truncate(); });

describe('POST /users', () => {
  it('creates a user and returns 201', async () => {
    const res = await request(app)
      .post('/users')
      .send({ email: 'test@test.com', name: 'Test' });

    expect(res.status).toBe(201);
    expect(res.body).toMatchObject({ email: 'test@test.com' });

    const user = await db('users').where({ email: 'test@test.com' }).first();
    expect(user).toBeDefined();
  });
});

// E2E test — Playwright
import { test, expect } from '@playwright/test';

test('user can sign up and see dashboard', async ({ page }) => {
  await page.goto('/signup');
  await page.fill('[name=email]', 'user@test.com');
  await page.fill('[name=password]', 'Password123!');
  await page.click('[type=submit]');

  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByText('Welcome')).toBeVisible();
});
Testing

Testing Interview Questions

Testing Interview Questions What's the difference between unit, integration, and E2E tests? Unit: isolated functions/components with mocked deps. Integration: m

Testing Interview Questions

  • What's the difference between unit, integration, and E2E tests? Unit: isolated functions/components with mocked deps. Integration: multiple units/layers together (API + DB). E2E: full user flows through real browser

  • What is test coverage and what % should you aim for? % of code exercised by tests. Aim for 80%+ for critical paths, not 100% everywhere. Coverage ≠ quality — tests should assert correct behaviour, not just execute lines

  • Mock vs stub vs spy? Stub: hard-coded return value. Mock: verifiable expectations on calls. Spy: wraps real implementation, records calls. jest.fn() is a mock/stub; jest.spyOn() is a spy

  • What is snapshot testing? Serializes component output to file, fails if it changes. Useful for catching unintended UI changes. Update with --updateSnapshot. Don't rely on it alone — it tests structure not behaviour

  • How do you test async code? Return promise or use async/await. Wrap assertions in waitFor for React Testing Library. Use fake timers (jest.useFakeTimers) for setTimeout/setInterval

  • What makes tests brittle? Testing implementation details (internal state, CSS classes, exact DOM structure). Prefer: user-visible behaviour, accessible roles/labels, semantic queries (getByRole, getByLabelText)

  • When would you skip TDD? Exploratory/prototyping code, UI layout, rapidly changing requirements. Write tests after stabilisation. TDD shines for business logic, algorithms, APIs

Keep your Testing knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever