testing-tdd-london

TDD London School (Mockist)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "testing-tdd-london" with this command: npx skills add vamseeachanta/workspace-hub/vamseeachanta-workspace-hub-testing-tdd-london

TDD London School (Mockist)

Outside-in, mock-driven development focusing on object collaborations and behavior verification

Quick Start

// 1. Start with acceptance test (outside) describe('User Registration', () => { it('should register new user successfully', async () => { const mockRepository = { save: jest.fn().mockResolvedValue({ id: '123' }) }; const mockNotifier = { sendWelcome: jest.fn() };

const service = new UserService(mockRepository, mockNotifier);
await service.register({ email: 'test@example.com' });

// 2. Verify behavior (interactions)
expect(mockRepository.save).toHaveBeenCalledWith(
  expect.objectContaining({ email: 'test@example.com' })
);
expect(mockNotifier.sendWelcome).toHaveBeenCalledWith('123');

}); });

When to Use

  • Testing object collaborations and message passing

  • Contract-driven development with clear interfaces

  • Outside-in development starting from user behavior

  • When isolation of units is critical

  • Service orchestration testing

  • Testing HOW objects work together (not WHAT they contain)

Prerequisites

  • Understanding of mock objects vs stubs

  • Jest, Vitest, or similar testing framework with mocking support

  • Clear separation of concerns in architecture

  • Dependency injection pattern in codebase

Core Concepts

London vs Chicago School

Aspect London (Mockist) Chicago (Classicist)

Focus Behavior/Interactions State

Isolation Mock all collaborators Use real objects

Direction Outside-in Inside-out

Test What HOW objects talk WHAT objects produce

Coupling To implementation To behavior

Outside-In Development Flow

Acceptance Test (failing) | v Controller Test (failing) | v Service Test (failing) | v Repository Test (failing) | v Implement (make tests pass from bottom up)

Mock Types

// Stub: Returns canned responses const stubRepo = { findById: jest.fn().mockResolvedValue(user) };

// Mock: Verifies interactions const mockNotifier = { send: jest.fn() }; // Later: expect(mockNotifier.send).toHaveBeenCalledWith(expectedArgs);

// Spy: Wraps real object, records calls const spyLogger = jest.spyOn(logger, 'info');

Implementation Pattern

  1. Outside-In Development

// Start with acceptance test (outermost layer) describe('User Registration Feature', () => { it('should register new user successfully', async () => { // Mock all collaborators const mockRepository = { save: jest.fn().mockResolvedValue({ id: '123', email: 'test@example.com' }), findByEmail: jest.fn().mockResolvedValue(null) };

const mockNotifier = {
  sendWelcome: jest.fn().mockResolvedValue(true)
};

const userService = new UserService(mockRepository, mockNotifier);
const result = await userService.register({
  email: 'test@example.com',
  password: 'secure123'
});

// Verify the conversation between objects
expect(mockRepository.findByEmail).toHaveBeenCalledWith('test@example.com');
expect(mockRepository.save).toHaveBeenCalledWith(
  expect.objectContaining({ email: 'test@example.com' })
);
expect(mockNotifier.sendWelcome).toHaveBeenCalledWith('123');
expect(result.success).toBe(true);

}); });

  1. Interaction Testing

describe('Order Processing', () => { it('should follow proper workflow interactions', async () => { const mockPayment = { charge: jest.fn().mockResolvedValue({ success: true }) }; const mockInventory = { reserve: jest.fn().mockResolvedValue(true) }; const mockShipping = { schedule: jest.fn().mockResolvedValue({ trackingId: 'ABC' }) };

const service = new OrderService(mockPayment, mockInventory, mockShipping);
await service.processOrder(order);

// Verify call order matters
const callOrder = [];
mockInventory.reserve.mockImplementation(() => {
  callOrder.push('reserve');
  return Promise.resolve(true);
});
mockPayment.charge.mockImplementation(() => {
  callOrder.push('charge');
  return Promise.resolve({ success: true });
});
mockShipping.schedule.mockImplementation(() => {
  callOrder.push('schedule');
  return Promise.resolve({ trackingId: 'ABC' });
});

await service.processOrder(order);
expect(callOrder).toEqual(['reserve', 'charge', 'schedule']);

}); });

  1. Contract Definition Through Mocks

// Define contracts for collaborators const userServiceContract = { register: { input: { email: 'string', password: 'string' }, output: { success: 'boolean', id: 'string' }, collaborators: ['UserRepository', 'NotificationService'], interactions: [ { method: 'findByEmail', args: ['email'], returns: 'null|User' }, { method: 'save', args: ['User'], returns: 'User' }, { method: 'sendWelcome', args: ['userId'], returns: 'boolean' } ] } };

// Generate mocks from contract function createMockFromContract(contract) { return Object.fromEntries( contract.interactions.map(i => [i.method, jest.fn()]) ); }

Configuration

london_tdd_config: testing: framework: jest mock_library: jest # or sinon, testdouble strict_mocks: true # fail on unexpected calls

coverage: interaction_coverage: true verify_all_mocks: true

swarm_coordination: share_contracts: true sync_mock_definitions: true

patterns: verify_call_order: true verify_call_count: true verify_call_args: true

Usage Examples

Example 1: Service Orchestration Test

describe('Service Collaboration', () => { let mockServiceA: jest.Mocked<ServiceA>; let mockServiceB: jest.Mocked<ServiceB>; let mockServiceC: jest.Mocked<ServiceC>; let orchestrator: ServiceOrchestrator;

beforeEach(() => { mockServiceA = { prepare: jest.fn().mockResolvedValue({ data: 'prepared' }) }; mockServiceB = { process: jest.fn().mockResolvedValue({ result: 'processed' }) }; mockServiceC = { finalize: jest.fn().mockResolvedValue({ status: 'complete' }) };

orchestrator = new ServiceOrchestrator(
  mockServiceA,
  mockServiceB,
  mockServiceC
);

});

it('should coordinate dependencies in correct order', async () => { await orchestrator.execute(task);

// Verify coordination sequence
expect(mockServiceA.prepare).toHaveBeenCalledBefore(mockServiceB.process);
expect(mockServiceB.process).toHaveBeenCalledBefore(mockServiceC.finalize);

// Verify data flow between services
expect(mockServiceB.process).toHaveBeenCalledWith(
  expect.objectContaining({ data: 'prepared' })
);
expect(mockServiceC.finalize).toHaveBeenCalledWith(
  expect.objectContaining({ result: 'processed' })
);

}); });

Example 2: Error Handling Verification

describe('Error Handling', () => { it('should handle repository failure gracefully', async () => { const mockRepository = { save: jest.fn().mockRejectedValue(new Error('Connection failed')) }; const mockLogger = { error: jest.fn() }; const mockRetry = { attempt: jest.fn().mockResolvedValue(false) };

const service = new UserService(mockRepository, mockLogger, mockRetry);

await expect(service.register(userData)).rejects.toThrow('Registration failed');

// Verify error handling interactions
expect(mockLogger.error).toHaveBeenCalledWith(
  'Repository save failed',
  expect.objectContaining({ error: expect.any(Error) })
);
expect(mockRetry.attempt).toHaveBeenCalledTimes(3);

}); });

Example 3: Swarm Coordination Testing

describe('Swarm Test Coordination', () => { let swarmCoordinator: SwarmCoordinator;

beforeAll(async () => { // Signal other swarm agents await swarmCoordinator.notifyTestStart('unit-tests'); });

afterAll(async () => { // Share test results with swarm await swarmCoordinator.shareResults(testResults); });

it('should share mock contracts across swarm', () => { const sharedMocks = { userRepository: createSwarmMock('UserRepository', { save: jest.fn(), findByEmail: jest.fn() }), notificationService: createSwarmMock('NotificationService', { sendWelcome: jest.fn() }) };

// Other swarm agents can verify against these contracts
swarmCoordinator.publishContracts(sharedMocks);

}); });

Execution Checklist

  • Write failing acceptance test (outside)

  • Define mock contracts for all collaborators

  • Write failing unit test for next layer

  • Implement minimal code to pass test

  • Verify all mock interactions

  • Refactor while keeping tests green

  • Move to next inner layer

  • Share contracts with swarm if coordinating

Best Practices

Mock Management

  • Keep mocks simple and focused on single behavior

  • Verify interactions, not implementation details

  • Use jest.fn() for behavior verification

  • Avoid over-mocking internal details

  • Reset mocks between tests

Contract Design

  • Define clear interfaces through mock expectations

  • Focus on object responsibilities and collaborations

  • Use mocks to DRIVE design decisions

  • Keep contracts minimal and cohesive

Common Pitfalls

// BAD: Over-mocking internal details const mock = { _internalState: {}, _privateMethod: jest.fn() // Don't mock private methods };

// GOOD: Mock only public interface const mock = { publicMethod: jest.fn().mockReturnValue(expectedResult) };

// BAD: Verifying too many implementation details expect(mock.method).toHaveBeenCalledTimes(3); // Fragile

// GOOD: Verify essential behavior expect(mock.method).toHaveBeenCalledWith(expectedArgs);

Error Handling

Missing Mock Verification

// Always verify mocks were called as expected afterEach(() => { // Fail if any mock was called unexpectedly expect(unexpectedCallsDetected()).toBe(false); });

// Use strict mocks const strictMock = jest.fn().mockImplementation(() => { throw new Error('Unexpected call'); });

Mock Leakage Between Tests

// Always reset mocks beforeEach(() => { jest.clearAllMocks(); // Clears call history // or jest.resetAllMocks(); // Also resets implementation });

Metrics & Success Criteria

Metric Target Description

Interaction Coverage 100% All collaborator calls verified

Mock Isolation 100% No real dependencies in unit tests

Contract Consistency 100% Mocks match real interfaces

Test Speed < 100ms Per test (no I/O)

Integration Points

MCP Tools

// Store successful test patterns action: "store", namespace: "test-patterns", key: "order_processing_mocks", value: JSON.stringify(mockDefinitions) });

// Share contracts across swarm action: "store", namespace: "test-contracts", key: "user_service_contract", value: JSON.stringify(userServiceContract) });

Hooks

Pre-test: Coordinate with swarm

Post-test: Share results

Related Skills

  • testing-production - Production validation

  • planning-code-goal - TDD integration in SPARC

  • webapp-testing - Web application testing

References

  • London School TDD

  • Mocks Aren't Stubs

  • Growing Object-Oriented Software

Version History

  • 1.0.0 (2026-01-02): Initial release - converted from tdd-london-swarm agent

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

echarts

No summary provided by upstream source.

Repository SourceNeeds Review
General

pandoc

No summary provided by upstream source.

Repository SourceNeeds Review
General

mkdocs

No summary provided by upstream source.

Repository SourceNeeds Review
General

gis

No summary provided by upstream source.

Repository SourceNeeds Review