askill
testing-strategy

testing-strategySafety --Repository

Test pyramid and testing strategy — unit, integration, and end-to-end test ratios, mocking strategies, test isolation, and what to test. Reference when planning test coverage or evaluating test quality.

3 stars
1.2k downloads
Updated 1/30/2026

Package Files

Loading files...
SKILL.md

Testing Strategy

Test Pyramid

        /  E2E  \          ~10% — Slow, brittle, expensive
       /  Integ  \         ~20% — Medium speed, real dependencies
      /   Unit    \        ~70% — Fast, isolated, numerous
     /______________\
LayerCountSpeedScopeRuns
Unit~70%< 10msSingle function or classEvery commit
Integration~20%< 5sMultiple components togetherEvery push
E2E~10%< 60sFull user workflowPre-merge, CI

If the pyramid inverts (more E2E than unit), treat it as a structural problem.

Unit Test Principles

FIRST Properties

PropertyRule
FastEntire unit suite runs in under 30 seconds
IsolatedNo test depends on another test's execution or state
RepeatableSame result every run, regardless of time, network, or OS
Self-validatingPass or fail with no manual inspection needed
TimelyWritten before or alongside the production code

What to Unit Test

  • Pure functions and their edge cases
  • Business logic and calculations
  • State transitions and validation rules
  • Data transformations and mappings
  • Error handling paths
  • Boundary conditions (empty, null, max, min, off-by-one)

What NOT to Unit Test

  • Framework configuration or boilerplate
  • Simple getters/setters with no logic
  • Third-party library internals
  • Private methods directly (test through public interface)

Arrange-Act-Assert

describe("calculateDiscount", () => {
  it("applies 10% discount for orders over $100", () => {
    // Arrange
    const order = { items: [{ price: 120, quantity: 1 }] };
    // Act
    const result = calculateDiscount(order);
    // Assert
    expect(result.discount).toBe(12);
    expect(result.total).toBe(108);
  });
});
class TestCalculateDiscount:
    def test_applies_10_percent_for_orders_over_100(self):
        order = Order(items=[Item(price=120, quantity=1)])
        result = calculate_discount(order)
        assert result.discount == 12
        assert result.total == 108

Integration Test Scope

CategoryExample
Database queriesRepository methods with a real (test) database
API endpointsHTTP request through middleware, handler, and response
External servicesCalls to third-party APIs with sandboxed accounts
Message queuesPublish and consume cycle with real broker
File systemRead/write operations with temp directories
Cache layersCache set, get, invalidation with real cache
describe("UserRepository", () => {
  let db: Database;
  beforeAll(async () => { db = await createTestDatabase(); await db.migrate(); });
  afterAll(async () => { await db.close(); });
  beforeEach(async () => { await db.truncateAll(); });

  it("finds user by email", async () => {
    await db.insert("users", { email: "test@example.com", name: "Test User" });
    const user = await userRepo.findByEmail("test@example.com");
    expect(user).not.toBeNull();
    expect(user.name).toBe("Test User");
  });
});

Integration Test Rules

  • Use a dedicated test database, never production data
  • Reset state between tests (truncate tables, clear queues)
  • Use test containers or in-memory databases where possible
  • Test the happy path and the most critical error paths

E2E Test Selection Criteria

Include: Critical revenue paths (signup, checkout, payment), authentication flows, core user journeys (3-5 things your product must do), cross-system workflows.

Exclude: Edge cases (cover in unit tests), UI-only validation, admin features, scenarios covered by lower-level tests.

test("user can complete checkout", async ({ page }) => {
  await page.goto("/login");
  await page.fill('[name="email"]', "user@test.com");
  await page.fill('[name="password"]', "password123");
  await page.click('button[type="submit"]');
  await page.goto("/products/widget-1");
  await page.click("text=Add to Cart");
  await page.goto("/cart");
  await page.click("text=Checkout");
  await page.fill('[name="card"]', "4242424242424242");
  await page.click("text=Place Order");
  await expect(page.locator(".order-confirmation")).toBeVisible();
});

Mocking Guidelines

When to Mock

MockReason
External HTTP APIsAvoid network dependency and rate limits
Time and date functionsEnable deterministic time-based tests
Random number generatorsEnable reproducible outputs
Email/SMS/notification servicesPrevent sending real messages
Payment processorsAvoid real charges during tests

When NOT to Mock

Do Not MockReason
The module under testYou would be testing your mock, not your code
Simple value objectsNo external dependency, no side effects
Standard library functionsTrusted, deterministic, fast
Database in integrationThe database interaction is what you are testing
Everything by defaultOver-mocking makes tests pass even when code breaks
// Good: mock the HTTP client, not the service
const mockHttpClient = { get: jest.fn().mockResolvedValue({ data: { id: 1, name: "Test" } }) };
const service = new UserService(mockHttpClient);

// Bad: mocking the method you are testing
jest.spyOn(service, "getUser").mockResolvedValue({ id: 1 });

Test Fixtures and Factories

Prefer factories over shared fixtures to keep tests independent.

function buildUser(overrides: Partial<User> = {}): User {
  return {
    id: randomUUID(),
    email: `user-${Date.now()}@test.com`,
    name: "Test User",
    role: "member",
    createdAt: new Date(),
    ...overrides,
  };
}
const admin = buildUser({ role: "admin" });
  • Generate unique values to prevent cross-test contamination
  • Use factories with sensible defaults and explicit overrides
  • Keep test data minimal; only set fields relevant to the assertion
  • Never share mutable state between tests

Property-Based Testing

Test invariants with randomly generated inputs.

import fc from "fast-check";

test("sort is idempotent", () => {
  fc.assert(fc.property(fc.array(fc.integer()), (arr) => {
    expect(sort(sort(arr))).toEqual(sort(arr));
  }));
});

test("encode/decode roundtrip", () => {
  fc.assert(fc.property(fc.string(), (input) => {
    expect(decode(encode(input))).toBe(input);
  }));
});

Use for: serialization roundtrips, sorting invariants, mathematical properties, parsers, encoding.

Snapshot Testing

Appropriate: Serialized output formats, generated config files, stable component render output. Harmful: Large frequently-changing outputs, UI with dynamic content, testing logic or behavior.

  • Review snapshot diffs carefully; never blindly update
  • Keep snapshots small and focused
  • Use inline snapshots for short outputs

Coverage Metrics

MetricTargetNotes
Line coverage>= 80%Reasonable baseline for most projects
Branch coverage>= 75%More meaningful than line coverage
Critical path>= 95%Auth, payments, data integrity
New code coverage>= 90%Enforce on PRs with coverage diff tools
  • Track coverage trends over time, not just absolute numbers
  • Never game coverage with meaningless tests
  • 100% coverage does not mean bug-free; focus on meaningful assertions
  • Exclude generated code, vendor code, and config from metrics

Testing Anti-Patterns

Anti-PatternProblemFix
Testing implementationBreaks when refactoring, even if behavior is sameTest behavior and outputs
Flaky testsErodes trust, gets ignoredFix or delete immediately
Slow test suiteDevelopers skip running testsParallelize, mock I/O, split by layer
Over-mockingTests pass but code is brokenMock boundaries only
Shared mutable stateTests pass alone, fail togetherIsolate state per test, use factories
Testing private methodsCouples tests to implementation detailsTest through the public API
No assertionTest passes without verifying anythingEvery test must assert something
Ignoring test failuresHides real bugsFix or remove; never skip permanently

Test Naming Conventions

// describe/it style
describe("ShoppingCart")
  it("calculates total with tax for items in cart")

// given/when/then style
test("given an empty cart, when adding an item, then total reflects item price")

// should style
test("should return 404 when user is not found")
  • Name describes the scenario and expected outcome
  • Name reads as a sentence or specification
  • Failed test name tells you what broke without reading the code

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

AI review pending.

Metadata

Licenseunknown
Version-
Updated1/30/2026
PublisherClaude-Code-Community-Ireland

Tags

apici-cddatabasegithub-actionsobservabilitysecuritytesting