mock-infrastructure-engineer
Mock Infrastructure Engineer
Quick Start
This skill optimizes mock infrastructure for E2E tests:
- Handler caching: Reduce mock setup from 1.7s to 200ms per test (88% faster)
- Fixture management: Centralize test data for consistency
- AI Gateway mocking: Mock Gemini API responses efficiently
When to Use
- Mock setup overhead exceeds 500ms per test
- E2E tests need AI Gateway mocking
- Test data scattered across files
- Inconsistent responses across test runs
Optimized Mock Pattern
Handler Caching System
This pattern achieved 88% performance improvement:
// tests/utils/mock-ai-gateway.ts
import type { Page, Route } from '@playwright/test';
interface GeminiMockConfig {
shouldFail?: boolean;
delay?: number;
customResponse?: any;
}
// Pre-built static response (Object.freeze prevents mutation)
const DEFAULT_MOCK_RESPONSE = Object.freeze({
candidates: [
{
content: {
parts: [{ text: 'This is a mocked AI response for testing purposes.' }],
role: 'model',
},
finishReason: 'STOP',
index: 0,
},
],
});
// Handler cache (reuse handlers across tests)
const handlerCache = new Map<string, (route: Route) => Promise<void>>();
function createGeminiRouteHandler(config: GeminiMockConfig = {}) {
const { shouldFail = false, delay = 0, customResponse } = config;
return async (route: Route) => {
if (delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay));
}
if (shouldFail) {
await route.abort('failed');
return;
}
const response = customResponse || DEFAULT_MOCK_RESPONSE;
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(response),
});
};
}
// Optimized setup function
export async function setupGeminiMock(
page: Page,
config: GeminiMockConfig = {},
): Promise<void> {
const cacheKey = JSON.stringify(config);
let handler = handlerCache.get(cacheKey);
if (!handler) {
handler = createGeminiRouteHandler(config);
handlerCache.set(cacheKey, handler);
}
await page.route('**/v1beta/models/**', handler);
}
Performance Results:
- Mock setup: 1.7s → 200ms (88% faster)
- Memory allocations: 110KB → 6KB (95% reduction)
- Handler creations: 55+ → 2-4 (96% reduction)
AI Gateway Response Patterns
Success Response
const successResponse = {
candidates: [
{
content: {
parts: [{ text: 'Generated content here' }],
role: 'model',
},
finishReason: 'STOP',
index: 0,
safetyRatings: [
{
category: 'HARM_CATEGORY_HARASSMENT',
probability: 'NEGLIGIBLE',
},
],
},
],
promptFeedback: { safetyRatings: [] },
};
Error Response
const errorResponse = {
error: {
code: 429,
message: 'Resource has been exhausted (e.g. check quota).',
status: 'RESOURCE_EXHAUSTED',
},
};
Streaming Response
async function handleStreamingRequest(route: Route) {
const chunks = [
'data: {"candidates":[{"content":{"parts":[{"text":"First "}]}}]}\\n\\n',
'data: {"candidates":[{"content":{"parts":[{"text":"chunk "}]}}]}\\n\\n',
'data: {"candidates":[{"content":{"parts":[{"text":"here"}]}}]}\\n\\n',
'data: [DONE]\\n\\n',
];
await route.fulfill({
status: 200,
contentType: 'text/event-stream',
body: chunks.join(''),
});
}
Fixture Management
Fixture Registry Pattern
// tests/fixtures/ai-responses.fixture.ts
export const aiResponseFixtures = {
outline: {
text: 'Chapter 1: Introduction\\nChapter 2: Rising Action\\nChapter 3: Climax',
metadata: { chapters: 3, wordCount: 15 },
},
character: {
text: 'Name: John Doe\\nAge: 35\\nBackground: Former detective',
metadata: { fields: 3 },
},
worldBuilding: {
text: 'Location: New York City\\nTime Period: 2024\\nSetting: Urban fantasy',
metadata: { elements: 3 },
},
};
// Usage in tests
import { aiResponseFixtures } from '../fixtures/ai-responses.fixture';
await setupGeminiMock(page, {
customResponse: {
candidates: [
{
content: {
parts: [{ text: aiResponseFixtures.outline.text }],
role: 'model',
},
},
],
},
});
Fixture Factory Pattern
// tests/fixtures/project.fixture.ts
export function createProjectFixture(
overrides: Partial<Project> = {},
): Project {
return {
id: crypto.randomUUID(),
title: 'Test Project',
description: 'Test project description',
genre: 'fantasy',
targetWordCount: 50000,
createdAt: Date.now(),
updatedAt: Date.now(),
...overrides,
};
}
// Usage
const project = createProjectFixture({ title: 'My Novel', genre: 'scifi' });
Global Setup for Performance
Browser Warm-Up
Add to tests/global-setup.ts for 66% faster first test:
import { chromium, type FullConfig } from '@playwright/test';
export default async function globalSetup(config: FullConfig): Promise<void> {
const browser = await chromium.launch();
await browser.close();
}
Configure in playwright.config.ts:
export default defineConfig({
globalSetup: require.resolve('./tests/global-setup'),
// ... rest of config
});
Test Examples
Example 1: Custom Response Mock
test('should generate character description', async ({ page }) => {
await setupGeminiMock(page, {
customResponse: {
candidates: [
{
content: {
parts: [
{
text: 'Name: Sarah Chen\\nAge: 28\\nOccupation: Software Engineer',
},
],
role: 'model',
},
finishReason: 'STOP',
},
],
},
});
await page.goto('/ai-generation');
await page.getByRole('button', { name: 'Generate Character' }).click();
await expect(page.getByText('Name: Sarah Chen')).toBeVisible();
});
Example 2: Error Handling
test('should handle AI generation failure', async ({ page }) => {
await setupGeminiMock(page, { shouldFail: true });
await page.goto('/ai-generation');
await page.getByRole('button', { name: 'Generate' }).click();
await expect(page.getByText('Generation failed')).toBeVisible();
await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible();
});
Example 3: Loading State
test('should show loading state during generation', async ({ page }) => {
await setupGeminiMock(page, { delay: 2000 });
await page.goto('/ai-generation');
await page.getByRole('button', { name: 'Generate' }).click();
// Loading indicator appears
await expect(page.getByTestId('loading-spinner')).toBeVisible();
// Wait for response
await expect(page.getByTestId('loading-spinner')).not.toBeVisible({
timeout: 5000,
});
await expect(page.getByTestId('generated-content')).toBeVisible();
});
Best Practices
Always Use Handler Caching
// ✅ Use caching
await setupGeminiMock(page, config);
// ❌ Create handler inline (slow)
await page.route('**/api/**', async route => {
// Handler creation on every call
});
Freeze Static Responses
// ✅ Frozen response (immutable)
const RESPONSE = Object.freeze({ data: 'value' });
// ❌ Mutable response (can be modified)
const response = { data: 'value' };
Use Fixtures for Test Data
// ✅ Reusable fixture
const project = createProjectFixture({ title: 'Test' });
// ❌ Inline test data (hard to maintain)
const project = {
id: '123',
title: 'Test',
createdAt: 1234567890,
// ... 20 more fields
};
Minimize Async Operations
// ✅ Single route registration
await page.route('**/api/**', cachedHandler);
// ❌ Multiple route registrations (slower)
await page.route('**/api/endpoint1', handler1);
await page.route('**/api/endpoint2', handler2);
await page.route('**/api/endpoint3', handler3);
Common Issues
Mock not intercepting requests
- Debug route matching:
page.route('**/*', route => console.log(route.request().url())) - Verify pattern matches actual request URL
Responses inconsistent across tests
- Use
Object.freeze()for static responses - Implement fixture factories for dynamic data
Mock setup still slow despite caching
- Verify cache is working:
console.log('Cache size:', handlerCache.size) - Should be small (2-4), not growing per test
Test isolation broken (state leaking)
- Clear cache between tests if needed:
handlerCache.clear() - Or use unique config per test
Success Metrics
- Mock setup time < 200ms per test
- Total mock overhead < 15s for full suite
- Cache hit rate > 90%
- Memory usage < 10KB per test
- Handler reuse: 96% reduction in creations
References
See tests/docs/ for detailed analysis:
- MOCK-OPTIMIZATION-GUIDE.md - Implementation patterns
- MOCK-PERFORMANCE-ANALYSIS.md - Optimization results
External documentation:
- Playwright Network Mocking: https://playwright.dev/docs/network
More from d-oit/do-novelist-ai
iterative-refinement
Execute iterative refinement workflows with validation loops until quality criteria are met. Use for test-fix cycles, code quality improvement, performance optimization, or any task requiring repeated action-validate-improve cycles.
11web-search-researcher
Research topics using web search and content fetching to find accurate, current information. Use when you need modern information, official documentation, best practices, technical solutions, or comparisons beyond your training data. Provides systematic web research with strategic searches, content analysis, and synthesized findings.
10agent-coordination
Coordinate multiple agents for software development across any language. Use for parallel execution of independent tasks, sequential chains with dependencies, swarm analysis from multiple perspectives, or iterative refinement loops. Handles Python, JavaScript, Java, Go, Rust, C#, and other languages.
9task-decomposition
Break down complex tasks into atomic, actionable goals with clear dependencies and success criteria. Use this skill when you need to plan multi-step projects, coordinate agents, or decompose complex user requests into manageable sub-tasks.
8gemini-websearch
Performs web searches using Gemini CLI headless mode with google_web_search tool. Includes intelligent caching, result validation, and analytics. Use when searching for current information, documentation, or when the user explicitly requests web search.
7skill-creator
Create new Claude Code skills with proper directory structure, SKILL.md file, and YAML frontmatter. Use this skill when you need to create a new reusable knowledge module for Claude Code.
6