claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
201 lines (179 loc) • 5.75 kB
text/typescript
/**
* V3 Claude-Flow Mock Factory
*
* London School TDD Mock Creation Utilities
* - Creates type-safe mocks for behavior verification
* - Supports deep mocking for complex objects
* - Enables interaction tracking for behavior testing
*/
import { vi, type Mock } from 'vitest';
/**
* Type for a fully mocked interface
*/
export type MockedInterface<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer R
? Mock<(...args: A) => R>
: T[K];
};
/**
* Create a shallow mock of an interface
* Each method becomes a vi.fn() for behavior verification
*
* @example
* const mockRepo = createMock<UserRepository>();
* mockRepo.findById.mockResolvedValue(user);
* expect(mockRepo.save).toHaveBeenCalledWith(user);
*/
export function createMock<T extends object>(): MockedInterface<T> {
return new Proxy({} as MockedInterface<T>, {
get: (target, prop: string | symbol) => {
if (typeof prop === 'string' && !(prop in target)) {
(target as Record<string | symbol, unknown>)[prop] = vi.fn();
}
return (target as Record<string | symbol, unknown>)[prop];
},
});
}
/**
* Create a deep mock that handles nested objects
* Useful for complex interfaces with nested dependencies
*
* @example
* const mockService = createDeepMock<ComplexService>();
* mockService.nested.method.mockReturnValue(result);
*/
export function createDeepMock<T extends object>(): MockedInterface<T> {
const cache = new Map<string | symbol, unknown>();
return new Proxy({} as MockedInterface<T>, {
get: (target, prop: string | symbol) => {
if (!cache.has(prop)) {
const mock = vi.fn();
// Allow chaining for nested access
(mock as unknown as Record<string, unknown>).mockReturnValue =
mock.mockReturnValue.bind(mock);
cache.set(prop, mock);
}
return cache.get(prop);
},
});
}
/**
* Create a spy mock that wraps an existing object
* Preserves original behavior while enabling verification
*
* @example
* const spied = createSpyMock(realService);
* await spied.process();
* expect(spied.process).toHaveBeenCalled();
*/
export function createSpyMock<T extends object>(target: T): MockedInterface<T> {
const spied = { ...target } as MockedInterface<T>;
for (const key of Object.keys(target) as Array<keyof T>) {
const value = target[key];
if (typeof value === 'function') {
(spied as Record<keyof T, unknown>)[key] = vi.fn(value.bind(target));
}
}
return spied;
}
/**
* Create a mock with predefined behavior
* Useful for common test scenarios
*
* @example
* const mockRepo = createMockWithBehavior<UserRepository>({
* findById: async (id) => ({ id, name: 'Test' }),
* save: async (user) => user,
* });
*/
export function createMockWithBehavior<T extends object>(
implementations: Partial<{ [K in keyof T]: T[K] }>
): MockedInterface<T> {
const mock = createMock<T>();
for (const [key, impl] of Object.entries(implementations)) {
if (typeof impl === 'function') {
(mock as Record<string, Mock>)[key].mockImplementation(impl as (...args: unknown[]) => unknown);
}
}
return mock;
}
/**
* Create a mock that fails on first call, succeeds on retry
* Useful for testing retry logic and error handling
*
* @example
* const mockApi = createRetryMock<ApiClient>('fetch', new Error('Network'), data);
*/
export function createRetryMock<T extends object>(
methodName: keyof T,
firstError: Error,
successValue: unknown
): MockedInterface<T> {
const mock = createMock<T>();
(mock as Record<string, Mock>)[methodName as string]
.mockRejectedValueOnce(firstError)
.mockResolvedValue(successValue);
return mock;
}
/**
* Create a sequence mock that returns different values per call
* Useful for testing stateful interactions
*
* @example
* const mockCounter = createSequenceMock<Counter>('next', [1, 2, 3, 4, 5]);
*/
export function createSequenceMock<T extends object>(
methodName: keyof T,
values: unknown[]
): MockedInterface<T> {
const mock = createMock<T>();
const fn = (mock as Record<string, Mock>)[methodName as string];
values.forEach((value, index) => {
if (index === values.length - 1) {
fn.mockReturnValue(value);
} else {
fn.mockReturnValueOnce(value);
}
});
return mock;
}
/**
* Interaction recorder for complex behavior verification
* Tracks all calls across multiple mocks
*
* @example
* const recorder = new InteractionRecorder();
* recorder.track('repo', mockRepo);
* recorder.track('notifier', mockNotifier);
* await service.process();
* expect(recorder.getInteractionOrder()).toEqual(['repo.save', 'notifier.notify']);
*/
export class InteractionRecorder {
private interactions: Array<{ name: string; method: string; args: unknown[]; timestamp: number }> = [];
track<T extends object>(name: string, mock: MockedInterface<T>): void {
for (const key of Object.keys(mock)) {
const method = (mock as Record<string, Mock>)[key];
if (typeof method?.mockImplementation === 'function') {
const original = method.getMockImplementation();
method.mockImplementation((...args: unknown[]) => {
this.interactions.push({
name,
method: key,
args,
timestamp: Date.now(),
});
return original?.(...args);
});
}
}
}
getInteractions(): Array<{ name: string; method: string; args: unknown[] }> {
return this.interactions.map(({ name, method, args }) => ({ name, method, args }));
}
getInteractionOrder(): string[] {
return this.interactions.map(({ name, method }) => `${name}.${method}`);
}
clear(): void {
this.interactions = [];
}
}