Node.js Testing: Complete Theory & Practice Guide

Build confidence with unit, integration, E2E, and robust mocking strategies.

JestSupertestPlaywright

Table of Contents

  1. Theory: Testing Fundamentals
  2. Basic: Unit Testing with Jest
  3. Advanced: Integration Testing
  4. Advanced: E2E Testing
  5. Advanced: Mocking & Stubs
  6. Best Practices
  7. Summary
  8. Interview Q&A + MCQ
  9. Contextual Learning Links

1. Theory: Testing Fundamentals

Why Testing Matters

Testing is the systematic verification that your code behaves as expected. It's not just about finding bugs—it's about building confidence to refactor, deploy, and scale your application.

              /\
             /  \
            /    \
           / E2E  \          ← Fewer tests, slower, higher confidence
          /--------\
         /          \
        / Integration \       ← Medium count, medium speed
       /--------------\
      /                \
     /      Unit         \    ← Most tests, fastest, isolated
    /--------------------\
LevelWhat It TestsSpeedMockingConfidence
UnitSingle function/class⚡ MillisecondsHeavyLow
IntegrationModule interactions📦 SecondsModerateMedium
E2EComplete user flows🐌 Seconds/minutesMinimalHigh
// test-types.js
const testTypes = {
    unit: {
        purpose: 'Verify individual functions/methods work correctly',
        example: 'expect(add(2, 3)).toBe(5)',
        characteristics: ['Fast', 'Isolated', 'Many', 'Cheap']
    },
    integration: {
        purpose: 'Verify database, API, and service interactions',
        example: 'expect(await userService.create(data)).toHaveProperty("id")',
        characteristics: ['Slower', 'Real dependencies', 'Fewer', 'Moderate cost']
    },
    e2e: {
        purpose: 'Verify critical user journeys work end-to-end',
        example: 'await page.click("#login"); await page.waitForNavigation()',
        characteristics: ['Slowest', 'Real browser', 'Very few', 'Expensive']
    },
    snapshot: {
        purpose: 'Catch unintended UI or output changes',
        example: 'expect(renderComponent()).toMatchSnapshot()',
        characteristics: ['Quick', 'Fragile', 'Use sparingly']
    },
    smoke: {
        purpose: 'Quick check that app is not completely broken',
        example: 'expect(await api.health()).toBe(200)',
        characteristics: ['Fast', 'Basic', 'Run on every deploy']
    },
    regression: {
        purpose: 'Verify fixed bugs stay fixed',
        example: 'Write test that reproduces bug, fix code, test passes',
        characteristics: ['Permanent', 'Specific', 'High value']
    }
};
// test-doubles.js
const testDoubles = {
    dummy: {
        description: 'Object with no implementation',
        useCase: 'Filling parameter lists',
        example: 'function test(dummy, callback) { callback(); }'
    },
    stub: {
        description: 'Provides predefined responses',
        useCase: 'Controlled test scenarios',
        example: 'database.getUser = jest.fn().mockReturnValue({ id: 1 })'
    },
    spy: {
        description: 'Wraps real function, records calls',
        useCase: 'Verify function was called with correct arguments',
        example: 'expect(sendEmail).toHaveBeenCalledWith("user@example.com")'
    },
    mock: {
        description: 'Fully controlled test double',
        useCase: 'Complex interaction verification',
        example: 'expect(mockAPI.getData).toHaveBeenCalledTimes(1)'
    },
    fake: {
        description: 'Lightweight alternative to real dependency',
        useCase: 'In-memory database for testing',
        example: 'new InMemoryDatabase() instead of PostgreSQL'
    }
};
┌─────────────────────────────────────────────────────────────┐
│                       70% Unit Tests                         │
│                   Fast, isolated, numerous                   │
├─────────────────────────────────────────────────────────────┤
│                      20% Integration                         │
│              Module interactions, databases, APIs           │
├─────────────────────────────────────────────────────────────┤
│                        10% E2E                               │
│                  Critical user journeys                     │
└─────────────────────────────────────────────────────────────┘
FrameworkBest ForKey Features
JestGeneral purposeZero config, snapshots, coverage
MochaFlexibilityCustomizable, many assertions
VitestVite projectsFast, ESM native
SupertestHTTP testingExpress/API testing
PlaywrightE2EMulti-browser, auto-wait
CypressE2ETime travel, real reloads
SinonMocks/spiesStandalone test doubles

2. Basic: Unit Testing with Jest

npm install --save-dev jest @types/jest
// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}
// math.js - Code to test
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }
function divide(a, b) {
    if (b === 0) throw new Error('Division by zero');
    return a / b;
}

// math.test.js - Tests
describe('Math operations', () => {
    test('adds 1 + 2 to equal 3', () => { expect(add(1, 2)).toBe(3); });
    test('subtracts 5 - 3 to equal 2', () => { expect(subtract(5, 3)).toBe(2); });
    test('multiplies 3 * 4 to equal 12', () => { expect(multiply(3, 4)).toBe(12); });
    test('divides 10 / 2 to equal 5', () => { expect(divide(10, 2)).toBe(5); });
    test('throws error when dividing by zero', () => {
        expect(() => divide(10, 0)).toThrow('Division by zero');
    });
});
// matchers.test.js
describe('Jest Matchers', () => {
    test('equality matchers', () => {
        expect(2 + 2).toBe(4);
        expect({ name: 'John' }).toEqual({ name: 'John' });
        expect([1, 2, 3]).toStrictEqual([1, 2, 3]);
    });
    test('truthiness matchers', () => {
        expect(true).toBeTruthy();
        expect(false).toBeFalsy();
        expect(null).toBeNull();
        expect(undefined).toBeUndefined();
        expect(0).toBeDefined();
    });
    test('numeric matchers', () => {
        expect(10).toBeGreaterThan(5);
        expect(10).toBeGreaterThanOrEqual(10);
        expect(5).toBeLessThan(10);
        expect(0.1 + 0.2).toBeCloseTo(0.3);
    });
    test('string matchers', () => {
        expect('Hello World').toMatch(/World/);
        expect('Hello World').toContain('Hello');
        expect('Hello World').toHaveLength(11);
    });
    test('array matchers', () => {
        expect([1, 2, 3]).toContain(2);
        expect([1, 2, 3]).toHaveLength(3);
        expect([1, 2, 3]).toEqual(expect.arrayContaining([2, 3]));
    });
    test('object matchers', () => {
        expect({ id: 1, name: 'John' }).toHaveProperty('name');
        expect({ id: 1, name: 'John' }).toHaveProperty('name', 'John');
        expect({ id: 1, name: 'John' }).toMatchObject({ name: 'John' });
    });
    test('exception matchers', () => {
        const throwError = () => { throw new Error('Failed'); };
        expect(throwError).toThrow();
        expect(throwError).toThrow('Failed');
        expect(throwError).toThrow(/Fail/);
    });
});
// async.js
async function fetchUser(id) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
}
function fetchWithCallback(id, callback) {
    setTimeout(() => callback(null, { id, name: 'John' }), 100);
}

// async.test.js
describe('Async testing', () => {
    test('resolves to user data', async () => {
        const user = await fetchUser(1);
        expect(user).toHaveProperty('name');
    });
    test('using .resolves matcher', () => {
        return expect(fetchUser(1)).resolves.toHaveProperty('name');
    });
    test('rejects on error', async () => {
        await expect(fetchUser(999)).rejects.toThrow();
    });
    test('callback receives data', (done) => {
        fetchWithCallback(1, (error, data) => {
            expect(error).toBeNull();
            expect(data.name).toBe('John');
            done();
        });
    });
});
// user-service.js
class UserService {
    constructor(database) { this.db = database; }
    async createUser(data) {
        if (!data.email) throw new Error('Email required');
        const existing = await this.db.findByEmail(data.email);
        if (existing) throw new Error('User exists');
        const user = { id: Date.now(), ...data, createdAt: new Date() };
        await this.db.save(user);
        return user;
    }
    async getUser(id) {
        const user = await this.db.findById(id);
        if (!user) throw new Error('User not found');
        return user;
    }
}
// user-service.test.js
describe('UserService', () => {
    let service;
    let mockDb;
    beforeEach(() => {
        mockDb = { findByEmail: jest.fn(), findById: jest.fn(), save: jest.fn() };
        service = new UserService(mockDb);
    });
    afterEach(() => { jest.clearAllMocks(); });
    test('creates user successfully', async () => {
        const userData = { email: 'test@example.com', name: 'Test' };
        mockDb.findByEmail.mockResolvedValue(null);
        mockDb.save.mockResolvedValue(true);
        const result = await service.createUser(userData);
        expect(result).toHaveProperty('id');
        expect(result.email).toBe('test@example.com');
        expect(mockDb.save).toHaveBeenCalledTimes(1);
    });
    test('throws error for duplicate email', async () => {
        mockDb.findByEmail.mockResolvedValue({ id: 1 });
        await expect(service.createUser({ email: 'existing@example.com' }))
            .rejects.toThrow('User exists');
    });
    test('throws error for missing email', async () => {
        await expect(service.createUser({ name: 'Test' }))
            .rejects.toThrow('Email required');
    });
});
// lifecycle.test.js
describe('Test lifecycle hooks', () => {
    beforeAll(() => { console.log('Setup database connection'); });
    beforeEach(() => { console.log('Reset test data'); });
    afterEach(() => { console.log('Clean up after test'); });
    afterAll(() => { console.log('Close database connection'); });
    test('test 1', () => { expect(true).toBe(true); });
    test('test 2', () => { expect(1 + 1).toBe(2); });
});
// snapshot.test.js
const user = { id: 1, name: 'John', email: 'john@example.com' };
test('user object matches snapshot', () => { expect(user).toMatchSnapshot(); });
test('user object matches inline snapshot', () => {
    expect(user).toMatchInlineSnapshot(`
        {
          "email": "john@example.com",
          "id": 1,
          "name": "John",
        }
    `);
});

3. Advanced: Integration Testing

npm install --save-dev supertest
// app.js + app.test.js (Supertest)
const request = require('supertest');
const app = require('./app');

describe('User API', () => {
    let createdUserId;
    test('POST /api/users - creates new user', async () => {
        const response = await request(app)
            .post('/api/users')
            .send({ name: 'John Doe', email: 'john@example.com' })
            .expect(201);
        expect(response.body).toHaveProperty('id');
        createdUserId = response.body.id;
    });
    test('GET /api/users/:id - returns user', async () => {
        const response = await request(app).get(`/api/users/${createdUserId}`).expect(200);
        expect(response.body.id).toBe(createdUserId);
    });
    test('DELETE /api/users/:id - removes user', async () => {
        await request(app).delete(`/api/users/${createdUserId}`).expect(204);
        await request(app).get(`/api/users/${createdUserId}`).expect(404);
    });
});
// db-service.js + db-service.test.js
const { Pool } = require('pg');

class UserRepository {
    constructor(pool) { this.pool = pool; }
    async create(user) {
        const result = await this.pool.query(
            'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
            [user.name, user.email]
        );
        return result.rows[0];
    }
    async findById(id) {
        const result = await this.pool.query('SELECT * FROM users WHERE id = $1', [id]);
        return result.rows[0];
    }
    async findAll() {
        const result = await this.pool.query('SELECT * FROM users ORDER BY id');
        return result.rows;
    }
    async clear() {
        await this.pool.query('TRUNCATE users RESTART IDENTITY');
    }
}
// testcontainers.test.js
const { GenericContainer } = require('testcontainers');
const { Pool } = require('pg');

describe('PostgreSQL with Testcontainers', () => {
    let container;
    let pool;
    beforeAll(async () => {
        container = await new GenericContainer('postgres:14')
            .withEnvironment({
                POSTGRES_USER: 'test',
                POSTGRES_PASSWORD: 'test',
                POSTGRES_DB: 'testdb'
            })
            .withExposedPorts(5432)
            .start();
        const port = container.getMappedPort(5432);
        pool = new Pool({ host: 'localhost', port, database: 'testdb', user: 'test', password: 'test' });
        await pool.query(`CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL)`);
    });
    afterAll(async () => {
        await pool.end();
        await container.stop();
    });
    test('database works in container', async () => {
        await pool.query('INSERT INTO users (name) VALUES ($1)', ['Test User']);
        const result = await pool.query('SELECT * FROM users');
        expect(result.rows).toHaveLength(1);
    });
});

4. Advanced: E2E Testing

npm install --save-dev @playwright/test
npx playwright install
// playwright.config.js
module.exports = {
    testDir: './e2e',
    timeout: 30000,
    use: {
        baseURL: 'http://localhost:3000',
        headless: true,
        screenshot: 'only-on-failure',
        video: 'retain-on-failure'
    },
    projects: [
        { name: 'chromium', use: { browserName: 'chromium' } },
        { name: 'firefox', use: { browserName: 'firefox' } },
        { name: 'webkit', use: { browserName: 'webkit' } }
    ]
};
// e2e/login.spec.js
const { test, expect } = require('@playwright/test');

test.describe('Login Flow', () => {
    test.beforeEach(async ({ page }) => { await page.goto('/login'); });
    test('successful login redirects to dashboard', async ({ page }) => {
        await page.fill('#email', 'user@example.com');
        await page.fill('#password', 'correct-password');
        await page.click('button[type="submit"]');
        await expect(page).toHaveURL('/dashboard');
        await expect(page.locator('.welcome-message')).toContainText('Welcome back');
    });
});
// e2e/todo.spec.js
test.describe('Todo App', () => {
    test.beforeEach(async ({ page }) => { await page.goto('/todos'); });
    test('adds new todo item', async ({ page }) => {
        await page.fill('#new-todo', 'Buy groceries');
        await page.press('#new-todo', 'Enter');
        const todoItem = page.locator('.todo-item:last-child');
        await expect(todoItem).toContainText('Buy groceries');
    });
    test('filters todos', async ({ page }) => {
        await page.fill('#new-todo', 'Active task');
        await page.press('#new-todo', 'Enter');
        await page.fill('#new-todo', 'Completed task');
        await page.press('#new-todo', 'Enter');
        await page.click('.todo-item:last-child .checkbox');
        await page.click('button:has-text("Active")');
        await expect(page.locator('.todo-item')).toHaveCount(1);
    });
});
// e2e/api.spec.js
const request = require('supertest');
const app = require('../app');

describe('E2E API Tests', () => {
    let authToken;
    test('user registration flow', async () => {
        const registerRes = await request(app)
            .post('/api/auth/register')
            .send({ email: 'e2e@example.com', password: 'Password123!', name: 'E2E Test' });
        expect(registerRes.status).toBe(201);
        const loginRes = await request(app)
            .post('/api/auth/login')
            .send({ email: 'e2e@example.com', password: 'Password123!' });
        expect(loginRes.status).toBe(200);
        authToken = loginRes.body.token;
    });
    test('protected endpoints require auth', async () => {
        await request(app).get('/api/users/me').expect(401);
    });
    test('CRUD operations flow', async () => {
        const createRes = await request(app)
            .post('/api/posts')
            .set('Authorization', `Bearer ${authToken}`)
            .send({ title: 'E2E Test Post', content: 'This is a test post' });
        expect(createRes.status).toBe(201);
    });
});

5. Advanced: Mocking & Stubs

// api-client.js / api-client.test.js
class APIClient {
    async fetchUsers() {
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        return response.json();
    }
    async createUser(data) {
        const response = await fetch('https://jsonplaceholder.typicode.com/users', {
            method: 'POST',
            body: JSON.stringify(data),
            headers: { 'Content-Type': 'application/json' }
        });
        return response.json();
    }
}

global.fetch = jest.fn();
describe('APIClient', () => {
    let client;
    beforeEach(() => { client = new APIClient(); fetch.mockClear(); });
    test('fetchUsers returns data', async () => {
        const mockUsers = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
        fetch.mockResolvedValueOnce({ ok: true, json: async () => mockUsers });
        const users = await client.fetchUsers();
        expect(users).toEqual(mockUsers);
    });
});
// module mocking
jest.mock('./email-service', () => ({
    sendWelcomeEmail: jest.fn().mockResolvedValue({ success: true })
}));

describe('UserController', () => {
    test('sends welcome email on registration', async () => {
        const controller = new UserController();
        const mockReq = { body: { email: 'test@example.com', name: 'Test' } };
        const mockRes = { json: jest.fn() };
        await controller.register(mockReq, mockRes);
        const { sendWelcomeEmail } = require('./email-service');
        expect(sendWelcomeEmail).toHaveBeenCalledWith('test@example.com');
    });
});
// __mocks__/database.js
const mockDb = {
    users: [],
    query: jest.fn(async (sql, params) => {
        if (sql.includes('INSERT INTO users')) {
            const user = { id: mockDb.users.length + 1, ...params[0] };
            mockDb.users.push(user);
            return { rows: [user] };
        }
        if (sql.includes('SELECT * FROM users')) return { rows: mockDb.users };
        return { rows: [] };
    }),
    clear: () => {
        mockDb.users = [];
        mockDb.query.mockClear();
    }
};
// sinon-example.test.js
const sinon = require('sinon');
describe('Sinon Mocks and Spies', () => {
    let sandbox;
    beforeEach(() => { sandbox = sinon.createSandbox(); });
    afterEach(() => { sandbox.restore(); });
    test('clock controls time', async () => {
        const clock = sandbox.useFakeTimers();
        const callback = sandbox.spy();
        setTimeout(callback, 1000);
        clock.tick(500);
        expect(callback.called).toBe(false);
        clock.tick(500);
        expect(callback.calledOnce).toBe(true);
        clock.restore();
    });
});

6. Best Practices

// aaa-pattern.test.js
describe('Calculate Total', () => {
    test('applies discount correctly', () => {
        // Arrange
        const items = [{ price: 100, quantity: 2 }, { price: 50, quantity: 1 }];
        const discount = 0.1;
        // Act
        const total = calculateTotal(items, discount);
        // Assert
        expect(total).toBe(225);
    });
});
// naming-conventions.test.js
describe('UserService', () => {
    test('createUser_withValidData_returnsUser', () => {});
    test('createUser_withDuplicateEmail_throwsError', () => {});
    test('createUser_withMissingEmail_throwsValidationError', () => {});
    test('should send welcome email when user registers', () => {});
    test('should return 404 when user not found', () => {});
    test('should reject request when token is invalid', () => {});
});
// jest.config.js
module.exports = {
    collectCoverage: true,
    coverageDirectory: 'coverage',
    coverageThreshold: {
        global: { branches: 80, functions: 80, lines: 80, statements: 80 },
        './src/critical/': { branches: 100, functions: 100, lines: 100, statements: 100 }
    },
    coveragePathIgnorePatterns: ['/node_modules/', '/__tests__/', '/migrations/', '/*.config.js'],
    coverageReporters: ['text', 'html', 'lcov', 'json'],
    testEnvironment: 'node',
    setupFilesAfterEnv: ['./jest.setup.js']
};
// package.json scripts
{
    "scripts": {
        "test": "jest",
        "test:watch": "jest --watch",
        "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
        "test:ci": "jest --ci --coverage --maxWorkers=2",
        "test:coverage": "jest --coverage && open coverage/lcov-report/index.html",
        "test:unit": "jest --testPathPattern=unit",
        "test:integration": "jest --testPathPattern=integration --runInBand",
        "test:e2e": "playwright test",
        "test:performance": "jest --testPathPattern=performance",
        "test:smoke": "jest --testPathPattern=smoke --bail",
        "test:update": "jest --updateSnapshot"
    }
}
// test-utils.js
class TestFactory {
    static createUser(overrides = {}) {
        return {
            id: Date.now(),
            name: 'Test User',
            email: `test_${Date.now()}@example.com`,
            createdAt: new Date(),
            ...overrides
        };
    }
}
class TestDatabase {
    constructor(connection) { this.connection = connection; this.transaction = null; }
    async beginTransaction() { this.transaction = await this.connection.beginTransaction(); return this.transaction; }
    async rollback() { if (this.transaction) { await this.transaction.rollback(); this.transaction = null; } }
}
class TestAPI {
    constructor(app) { this.app = app; this.authToken = null; }
    async login(email, password) {
        const response = await request(this.app).post('/api/auth/login').send({ email, password });
        this.authToken = response.body.token;
        return this.authToken;
    }
}
// checklist.js
const testingChecklist = {
    unit: [
        '✅ Test happy path',
        '✅ Test edge cases (null, undefined, empty)',
        '✅ Test error conditions',
        '✅ Test boundary values',
        '✅ Each test tests ONE thing',
        '✅ Tests are independent',
        '✅ No network/database calls',
        '✅ Mock external dependencies'
    ],
    integration: [
        '✅ Test database operations',
        '✅ Test API endpoints',
        '✅ Test error responses',
        '✅ Test authentication',
        '✅ Test request validation',
        '✅ Use test database',
        '✅ Clean up after tests',
        '✅ Test real implementations not mocks'
    ],
    e2e: [
        '✅ Test critical user journeys',
        '✅ Test across browsers',
        '✅ Test responsive design',
        '✅ Test error UI states',
        '✅ Test loading states',
        '✅ Test form submissions',
        '✅ Test navigation flows',
        '✅ Run in CI pipeline'
    ],
    performance: [
        '✅ Load testing for critical endpoints',
        '✅ Stress testing for peak loads',
        '✅ Response time benchmarks',
        '✅ Memory leak detection',
        '✅ Database query performance',
        '✅ Caching effectiveness'
    ]
};

Summary

Test TypeToolSpeedConfidenceWhen to Use
UnitJest⚡⚡⚡LowEvery function/class
IntegrationSupertest⚡⚡MediumAPI routes, DB queries
ComponentTesting Library⚡⚡MediumReact/Vue components
E2EPlaywrightHighCritical user journeys
SmokeJest⚡⚡⚡MediumPre-deployment checks
RegressionJest⚡⚡HighAfter bug fixes
# Run tests
npm test

# Watch mode (auto-run on changes)
npm run test:watch

# Coverage report
npm run test:coverage

# Run specific test file
npm test -- user-service.test.js

# Run tests matching pattern
npm test -- -t "creates user"

# Debug mode
npm run test:debug

# Update snapshots
npm run test:update

# CI mode (non-watch)
npm run test:ci

10 Interview Questions + 10 MCQs

1What is the testing pyramid?easy
Answer: A strategy with many unit tests, fewer integration tests, and very few E2E tests.
2Why keep unit tests isolated?easy
Answer: Isolation makes tests fast, deterministic, and easier to debug.
3When should integration tests be used?medium
Answer: To verify interactions between modules, DB, APIs, and middleware.
4What does Supertest provide?easy
Answer: HTTP assertions for Express/Node APIs without spinning a full external client.
5What is a test double?medium
Answer: A substitute for a dependency (dummy, stub, spy, mock, fake).
6Why avoid testing private methods directly?medium
Answer: It couples tests to implementation details and breaks refactoring safety.
7What is snapshot testing best for?easy
Answer: Detecting unintended output/UI changes in stable components.
8Why run tests in CI?easy
Answer: To catch regressions automatically before deployment.
9What makes E2E tests expensive?medium
Answer: Full-stack/browser setup, slower execution, and higher maintenance.
10How should coverage be interpreted?hard
Answer: As a signal, not a goal; high coverage does not guarantee meaningful assertions.

10 Testing MCQs

1

Most tests should be:

AUnit tests
BE2E tests
CManual tests
DUI snapshots only
Explanation: Unit tests are fast and numerous.
2

Jest is mainly used for:

ANode package publishing
BTesting and assertions
CContainer orchestration
DLinting only
Explanation: Jest is a test runner + assertion/mocking framework.
3

Supertest is best for:

AAPI endpoint testing
BCSS snapshots
CDB migrations
DType checking
Explanation: Supertest targets HTTP route testing.
4

A stub usually:

AReturns predefined values
BRuns production DB
CCompiles TS
DFormats code
Explanation: Stubs provide canned responses.
5

Playwright is primarily for:

AE2E browser tests
BAPI schema generation
CServer deployment
DImage optimization
Explanation: Playwright automates real browsers.
6

AAA pattern means:

AArrange, Act, Assert
BAnalyze, Add, Apply
CAsk, Answer, Approve
DAnnotate, Assign, Archive
Explanation: AAA is a clear test structure pattern.
7

Good test naming should:

ADescribe scenario and outcome
BBe very short and vague
CUse random IDs
DMatch file hash
Explanation: Readable names document behavior and intent.
8

Integration tests should use:

AReal interactions where practical
BOnly mocked everything
CNo assertions
DOnly console logs
Explanation: Integration tests validate modules working together.
9

A spy is used to:

ATrack function calls
BStart Docker containers
CCompile code
DSort arrays
Explanation: Spies observe call counts/arguments.
10

Coverage is:

AA quality signal, not complete quality
BGuaranteed bug-free proof
COnly for frontend
DOptional in CI always
Explanation: Assertions and scenario quality matter more than percentage alone.