Agent Skills: Test Levels Guide

This skill explains the 3 test levels (Unit, Integration, E2E) using the "Building a Car" analogy and provides guidance on when to use each type. Includes project-specific Playwright examples.

UncategorizedID: georgekhananaev/claude-skills-vault/test-levels

Install this agent skill to your local

pnpm dlx add-skill https://github.com/georgekhananaev/claude-skills-vault/tree/HEAD/.claude/skills/test-levels

Skill Files

Browse the full folder contents for test-levels.

Download Skill

Loading file tree…

.claude/skills/test-levels/SKILL.md

Skill Metadata

Name
test-levels
Description
This skill explains the 3 test levels (Unit, Integration, E2E) using the "Building a Car" analogy and provides guidance on when to use each type. Includes project-specific Playwright examples.

Test Levels Guide

Explains test types & guides test selection using car analogy.

When to Use

Invoke when:

  • Explaining test concepts to team members
  • Deciding which test type to write
  • Reviewing test coverage strategy
  • Onboarding new developers to testing

The 3 Test Levels

1. Unit Test (Test Case)

The specific instruction.

Single fn/component tested in isolation. No external deps (DB, API, browser).

| Aspect | Description | |--------|-------------| | Analogy | "Check if left turn signal blinks when I push lever down" | | Scope | Tiny detail - one fn, one input/output | | Speed | Fast (ms) | | Location | tests/unit/ |

When to write:

  • Pure functions (formatters, validators, utils)
  • Data transformations
  • Business logic w/o side effects

Project example:

// tests/unit/utils/formatters.spec.ts
import {expect, test} from 'next/experimental/testmode/playwright';
import {leadingZero, formatCurrency} from '@/app/_utils/formatters';

test.describe('formatters', () => {
    test('leadingZero adds zero to single-digit numbers', () => {
        expect(leadingZero(7)).toBe('07');
        expect(leadingZero(10)).toBe('10');
    });

    test('formatCurrency formats correctly', () => {
        const result = formatCurrency({value: 1234.56, locale: 'en', currency: 'USD'});
        expect(result).toBe('1,234.56 $');
    });
});

2. Integration Test

The handshake.

Tests if 2+ parts communicate correctly. Focuses on connections, not full system.

| Aspect | Description | |--------|-------------| | Analogy | "Does engine make wheels turn?" (Engine → Transmission) | | Scope | Connections between components | | Speed | Medium (100ms-few seconds) | | Location | tests/integration/ |

When to write:

  • Validators w/ schemas (Zod)
  • API route handlers
  • Service-to-service communication
  • DB queries w/ mocked data

Project example:

// tests/integration/validators/offer.spec.ts
import {expect, test} from 'next/experimental/testmode/playwright';
import {mockLoggedUser} from '../../common';
import {validateCustomerStatus} from '@/app/_lib/validator';

test.beforeEach(async ({context}) => {
    await mockLoggedUser(context);
});

test.describe('validate customer status', () => {
    const validData = {
        offerId: '670e80f0a65da593d265088a',
        status: 'viewing',
    };

    test('returns success for valid data', async () => {
        const result = validateCustomerStatus(validData);
        expect(result.success).toBe(true);
        expect(result.data).toEqual(validData);
    });

    test('returns error for invalid offerId', async () => {
        const result = validateCustomerStatus({...validData, offerId: ''});
        expect(result.success).toBe(false);
        expect(result.error?.issues[0]?.path).toContain('offerId');
    });
});

3. E2E Test (End-to-End)

The real user journey.

Full system test: browser, DB, network, 3rd-party services. Exactly as user experiences.

| Aspect | Description | |--------|-------------| | Analogy | "Start car, drive to store, park, turn off" | | Scope | Full user flow | | Speed | Slow (seconds-minutes) | | Location | tests/pages/ |

When to write:

  • Critical user flows (login, checkout, payment)
  • Multi-page journeys
  • Features requiring browser interaction
  • Smoke tests for deployment

Project example:

// tests/pages/start.spec.ts
import {expect, test} from 'next/experimental/testmode/playwright';
import {mockLoggedUser, resetAPIEndpointsMock} from '../common';

test.beforeEach(async ({context}) => {
    await mockLoggedUser(context);
});

test.afterEach(async ({next, context}) => {
    await resetAPIEndpointsMock(next);
    await context.clearCookies();
});

test('start page renders correctly', async ({page}) => {
    test.setTimeout(120000);

    await page.goto('http://localhost:3000/start', {
        timeout: 90000,
        waitUntil: 'domcontentloaded'
    });

    await expect(page).toHaveURL('http://localhost:3000/start');
    await expect(page.getByTestId('pageHeader')).toHaveClass('drop-shadow-font');
    await expect(page.getByTestId('subTotal')).toContainText('Sub total');
    await expect(page.getByTestId('startCounter')).toContainText('Your vacation starts in');
});

Quick Decision Guide

| Question | Test Type | |----------|-----------| | "Does this fn return correct value?" | Unit | | "Do these 2 parts work together?" | Integration | | "Does full flow work for user?" | E2E |

Test Pyramid

        /\
       /E2E\         Few (slow, expensive)
      /------\
     /Integr- \      Some (medium)
    /  ation   \
   /------------\
  /    Unit      \   Many (fast, cheap)
 /________________\

Rule: More unit tests, fewer E2E tests. Unit tests catch bugs early & run fast.


Project Structure

tests/
├── unit/              # Pure fn tests (no browser)
│   └── utils/         # Utility fn tests
├── integration/       # Component interaction tests
│   ├── validators/    # Schema validation tests
│   └── lib/           # Library fn tests
├── pages/             # E2E browser tests
│   ├── start.spec.ts
│   ├── payment.spec.ts
│   └── confirm.spec.ts
├── common.ts          # Shared test utilities
├── mock/              # Mock data & helpers
└── seed.spec.ts       # DB seed for tests

Commands

# Run all tests (headed mode)
npm run test

# Run specific test file
npm run test tests/unit/utils/formatters.spec.ts

# Run tests in UI mode
npm run test:ui

# Show test report
npm run test:report

Best Practices

  1. Name tests clearly: should [action] when [condition]
  2. One assertion focus: Test one behavior per test
  3. Use test.describe: Group related tests
  4. Clean up: Use afterEach for state reset
  5. Mock external deps: Use mockLoggedUser, resetAPIEndpointsMock
  6. Set timeouts: E2E tests need longer timeouts (120s)
  7. Use data-testid: For reliable element selection

Summary Table

| Level | Question | Scope | Speed | Location | |-------|----------|-------|-------|----------| | Unit | "Does this fn work?" | Single fn | Fast | tests/unit/ | | Integration | "Do parts connect?" | Connections | Medium | tests/integration/ | | E2E | "Does flow work?" | Full system | Slow | tests/pages/ |