T

Testing Patterns Studio

Comprehensive skill designed for jest, testing, patterns, factory. Includes structured workflows, validation checks, and reusable patterns for development.

SkillClipticsdevelopmentv1.0.0MIT
0 views0 copies

Testing Patterns Studio

A comprehensive skill for testing patterns across unit, integration, and end-to-end testing. Covers test organization, mocking strategies, test data management, assertion patterns, and framework-agnostic best practices.

When to Use This Skill

Choose this skill when:

  • Establishing testing patterns and conventions for a project
  • Choosing the right test types for different code layers
  • Implementing mocking strategies for external dependencies
  • Setting up test data factories and fixtures
  • Writing maintainable, readable, and reliable tests

Consider alternatives when:

  • Practicing TDD methodology → use a TDD workflow skill
  • Fixing failing tests → use a test-fixing skill
  • Running end-to-end browser tests → use a Playwright or Cypress skill
  • Setting up CI/CD test pipelines → use a DevOps skill

Quick Start

// Test file organization following Arrange-Act-Assert import { describe, it, expect, beforeEach, vi } from 'vitest'; import { OrderService } from './OrderService'; import { createFakeOrderRepo } from '../test-utils/fakes'; import { buildOrder } from '../test-utils/builders'; describe('OrderService', () => { let service: OrderService; let repo: FakeOrderRepo; beforeEach(() => { repo = createFakeOrderRepo(); service = new OrderService(repo); }); describe('submitOrder', () => { it('sets status to submitted and saves', async () => { // Arrange const order = buildOrder({ status: 'draft', itemCount: 3 }); await repo.save(order); // Act await service.submitOrder(order.id); // Assert const saved = await repo.findById(order.id); expect(saved?.status).toBe('submitted'); }); it('throws when order is empty', async () => { const order = buildOrder({ status: 'draft', itemCount: 0 }); await repo.save(order); await expect(service.submitOrder(order.id)) .rejects.toThrow('Cannot submit empty order'); }); }); });

Core Concepts

Test Type Selection Guide

Code LayerTest TypeToolsSpeed
Pure functionsUnit testVitest, Jest< 1ms
Services with depsUnit + mocks/fakesVitest + fakes< 10ms
API endpointsIntegration testSupertest< 100ms
Database queriesIntegration testTestcontainers< 500ms
UI componentsComponent testTesting Library< 100ms
Full user flowsE2E testPlaywright1-10s

Test Data Builder Pattern

// Flexible builder with sensible defaults interface OrderBuilder { id: string; customerId: string; status: OrderStatus; items: OrderItem[]; total: number; createdAt: Date; } function buildOrder(overrides: Partial<OrderBuilder> = {}): Order { const itemCount = overrides.itemCount ?? 1; const items = Array.from({ length: itemCount }, (_, i) => ({ productId: `prod-${i}`, name: `Product ${i}`, price: 29.99, quantity: 1, })); return { id: `order-${crypto.randomUUID().slice(0, 8)}`, customerId: 'cust-default', status: 'draft', items, total: items.reduce((sum, i) => sum + i.price * i.quantity, 0), createdAt: new Date('2024-01-15'), ...overrides, }; } // Usage in tests: const draftOrder = buildOrder(); // sensible defaults const bigOrder = buildOrder({ itemCount: 50 }); const submittedOrder = buildOrder({ status: 'submitted' });

Mocking Strategy Decision Tree

# When to use which test double: 1. External HTTP APIs → **Stub** the HTTP client or use MSW 2. Database → **Fake** with in-memory implementation 3. Time/Date → **Stub** `Date.now()` or use `vi.useFakeTimers()` 4. File system → **Fake** with in-memory fs (memfs) 5. Environment variables → **Stub** `process.env` 6. Third-party SDK → **Mock** with simplified interface 7. Internal collaborator → **Fake** with real interface implementation
// MSW (Mock Service Worker) for API mocking import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; const server = setupServer( http.get('/api/users/:id', ({ params }) => { return HttpResponse.json({ id: params.id, name: 'Test User', email: '[email protected]', }); }), http.post('/api/orders', async ({ request }) => { const body = await request.json(); return HttpResponse.json({ id: 'new-order', ...body }, { status: 201 }); }) ); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close());

Configuration

ParameterTypeDefaultDescription
testRunnerstring'vitest'Runner: vitest, jest, mocha, or pytest
assertionStylestring'expect'Assertions: expect, assert, or should
mockStrategystring'fakes-first'Default: fakes-first, mocks-first, or mixed
dataStrategystring'builders'Test data: builders, fixtures, or factories
coverageReporterstring'v8'Coverage: v8, istanbul, or c8
isolationLevelstring'test'Isolation: test (reset each) or suite

Best Practices

  1. Follow the Arrange-Act-Assert pattern in every test — Structure tests with clear setup (Arrange), a single action (Act), and focused verification (Assert). This makes tests scannable and failures diagnosable.

  2. Use test data builders with sensible defaults — Builders let each test specify only the fields relevant to its concern. Default values keep tests concise and prevent coupling to unrelated data requirements.

  3. Prefer fakes over mocks for dependencies — A FakeUserRepository backed by a Map is simpler, more realistic, and more refactor-resistant than a mock with when().thenReturn() chains. Mocks verify interactions; fakes verify behavior.

  4. One test, one reason to fail — Each test should verify a single behavior. When a test has 5 assertions checking different things, a failure doesn't tell you what broke. Split into focused tests with descriptive names.

  5. Name tests as behavior specificationsit('should return 404 when user not found') is more useful than it('test getUserById'). Good test names serve as living documentation of system behavior.

Common Issues

Tests are slow because they hit real databases — Replace real database calls with in-memory fakes for unit tests. Use Testcontainers for integration tests that need real database behavior. Never connect to shared development databases in tests.

Mocking makes tests pass but bugs reach production — Over-mocking replaces real behavior with assumed behavior. The mock always returns success, but the real service returns errors. Use fakes that implement the real interface, and write integration tests that verify real interactions.

Test suite order matters (tests pass individually, fail together) — Shared mutable state leaks between tests. Add beforeEach to reset all state, use unique IDs per test, and run tests with --randomize flag to detect order dependencies early.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates