Testing Patterns Studio
Comprehensive skill designed for jest, testing, patterns, factory. Includes structured workflows, validation checks, and reusable patterns for development.
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 Layer | Test Type | Tools | Speed |
|---|---|---|---|
| Pure functions | Unit test | Vitest, Jest | < 1ms |
| Services with deps | Unit + mocks/fakes | Vitest + fakes | < 10ms |
| API endpoints | Integration test | Supertest | < 100ms |
| Database queries | Integration test | Testcontainers | < 500ms |
| UI components | Component test | Testing Library | < 100ms |
| Full user flows | E2E test | Playwright | 1-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
| Parameter | Type | Default | Description |
|---|---|---|---|
testRunner | string | 'vitest' | Runner: vitest, jest, mocha, or pytest |
assertionStyle | string | 'expect' | Assertions: expect, assert, or should |
mockStrategy | string | 'fakes-first' | Default: fakes-first, mocks-first, or mixed |
dataStrategy | string | 'builders' | Test data: builders, fixtures, or factories |
coverageReporter | string | 'v8' | Coverage: v8, istanbul, or c8 |
isolationLevel | string | 'test' | Isolation: test (reset each) or suite |
Best Practices
-
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.
-
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.
-
Prefer fakes over mocks for dependencies — A
FakeUserRepositorybacked by aMapis simpler, more realistic, and more refactor-resistant than a mock withwhen().thenReturn()chains. Mocks verify interactions; fakes verify behavior. -
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.
-
Name tests as behavior specifications —
it('should return 404 when user not found')is more useful thanit('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.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.