agentleFS
AdhamxonAdhamxon/claude-code-skills/.claude/skills/testing-strategies/SKILL.md

testing-strategies

Skill2 starsChanged 4 months agoMITView on GitHub

Testing Strategies — unit tests, integration tests, E2E testing, contract testing, visual regression, property-based testing, mutation testing, test design patterns, CI/CD integration. Use when designing test strategies or writing comprehensive tests.

8 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

1 rule. 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 6 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Isolated: No shared state, independent order

    line 53

“Common” is measured: kept by at least 6 in 10 of the 6 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Testing Strategies Skill

Test Pyramid (Practical)

         ╱╲
        ╱ E2E ╲           ~5% — Critical user journeys
       ╱────────╲
      ╱ Integration ╲     ~15% — API, DB, external services
     ╱────────────────╲
    ╱    Unit Tests      ╲  ~80% — Isolated functions, components
   ╱────────────────────────╲

Unit Testing

AAA Pattern

// Arrange-Act-Assert
describe('UserService.register', () => {
  it('should create user and send welcome email', async () => {
    // Arrange
    const emailService = { sendWelcome: vi.fn() };
    const repo = { findByEmail: vi.fn().mockResolvedValue(null), save: vi.fn() };
    const service = new UserService(repo, emailService);

    // Act
    const user = await service.register({ email: 'test@test.com', name: 'Test' });

    // Assert
    expect(user.email).toBe('test@test.com');
    expect(repo.save).toHaveBeenCalledWith(expect.objectContaining({ email: 'test@test.com' }));
    expect(emailService.sendWelcome).toHaveBeenCalledWith(user.id);
  });
});

Test Doubles Guide

TypeDescriptionWhen to Use
DummyPassed but not usedFilling parameter lists
FakeWorking implementation (in-memory DB)Slower tests but more confidence
StubReturns canned answersWhen you need a specific response
SpyRecords calls madeVerifying interactions
MockPre-programmed with expectationsBehavior verification

F.I.R.S.T Principles

  • Fast: Tests should run quickly (< 100ms per test)
  • Isolated: No shared state, independent order
  • Repeatable: Same result every time
  • Self-validating: Pass/fail, no manual check
  • Timely: Written before or alongside production code

Integration Testing

Database Integration (Testcontainers)

import { PostgreSqlContainer } from '@testcontainers/postgresql';

describe('UserRepository', () => {
  let container: StartedPostgreSqlContainer;
  let pool: Pool;

  beforeAll(async () => {
    container = await new PostgreSqlContainer('postgres:16-alpine')
      .withDatabase('testdb')
      .start();
    pool = new Pool({ connectionString: container.getConnectionUri() });
    await runMigrations(pool);
  }, 30000);

  afterAll(async () => {
    await pool.end();
    await container.stop();
  });

  it('should persist and retrieve user', async () => {
    const repo = new UserRepository(pool);
    const user = await repo.create({ email: 'test@test.com' });
    expect(user.id).toBeDefined();

    const found = await repo.findById(user.id);
    expect(found.email).toBe('test@test.com');
  });
});

API Integration (Supertest)

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

describe('POST /api/auth/login', () => {
  it('should return 200 with token for valid credentials', async () => {
    const res = await request(app)
      .post('/api/auth/login')
      .send({ email: 'admin@test.com', password: 'password123' })
      .expect(200);

    expect(res.body).toHaveProperty('token');
    expect(res.body.user.email).toBe('admin@test.com');
  });

  it('should return 401 for invalid password', async () => {
    await request(app)
      .post('/api/auth/login')
      .send({ email: 'admin@test.com', password: 'wrong' })
      .expect(401);
  });
});

E2E Testing (Playwright)

Page Object Model

class LoginPage {
  constructor(private page: Page) {}

  async goto() { await this.page.goto('/login'); }
  async fillEmail(email: string) { await this.page.fill('[data-testid="email"]', email); }
  async fillPassword(password: string) { await this.page.fill('[data-testid="password"]', password); }
  async submit() { await this.page.click('[data-testid="login-button"]'); }
  async getErrorMessage() { return this.page.textContent('[data-testid="error"]'); }
}

test('successful login', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.fillEmail('user@test.com');
  await loginPage.fillPassword('correct-password');
  await loginPage.submit();
  await expect(page).toHaveURL('/dashboard');
});

API Mocking (MSW)

import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  http.get('https://api.example.com/users/:id', ({ params }) => {
    return HttpResponse.json({ id: params.id, name: 'Test User' });
  }),
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Contract Testing (Pact)

// Provider
await provider.verify({
  state: 'user exists',
  uponReceiving: 'a request for user',
  withRequest: { method: 'GET', path: '/users/123' },
  willRespondWith: { status: 200, body: { id: '123', name: 'Test' } },
});

// Consumer
await pact.addInteraction({
  state: 'user exists',
  uponReceiving: 'get user by id',
  withRequest: { method: 'GET', path: '/users/123' },
  willRespondWith: { status: 200, headers: { 'Content-Type': 'application/json' } },
});

Performance Testing (k6)

import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend } from 'k6/metrics';

const errorRate = new Rate('errors');
const latency = new Trend('latency_ms');

export const options = {
  stages: [
    { duration: '2m', target: 50 },    // Ramp up
    { duration: '5m', target: 50 },    // Stay
    { duration: '2m', target: 100 },   // Spike
    { duration: '2m', target: 0 },     // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    errors: ['rate<0.01'],
  },
};

export default function () {
  group('user API', () => {
    const res = http.get('https://api.example.com/users');
    latency.add(res.timings.duration);
    errorRate.add(res.status !== 200);
    check(res, { 'status 200': (r) => r.status === 200 });
  });
  sleep(1);
}

Visual Regression Testing

// Playwright visual comparison
test('homepage matches snapshot', async ({ page }) => {
  await page.goto('/');
  await page.waitForLoadState('networkidle');
  await expect(page).toHaveScreenshot('homepage.png', {
    maxDiffPixelRatio: 0.01,
    threshold: 0.2,
  });
});

Mutation Testing (Stryker)

npx stryker run
# Killed mutants: 385/420 = 91.7% mutation score
# Goal: > 80% mutation score

Coverage Goals

TypeLine CoverageBranch CoverageMutation Score
Core domain100%100%95%+
Services95%+90%+90%+
Components90%+85%+85%+
Controllers/API90%+80%+80%+
Configuration60%+50%+50%+

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.