| name | testing-strategy |
| description | Node.js testing with Vitest/Jest, MCP protocol testing, European Parliament API mocking, integration tests, and 80%+ coverage |
| license | MIT |
Testing Strategy Skill
Context
This skill applies when:
- Writing unit tests for functions, classes, or modules
- Creating integration tests for MCP protocol handlers
- Mocking European Parliament API responses
- Testing input validation and error handling
- Writing tests for async/await code and Promises
- Implementing test fixtures and test data
- Measuring and improving code coverage
- Testing security controls and edge cases
- Debugging failing or flaky tests
- Setting up test infrastructure and CI/CD
Testing is mandatory for all code. We maintain 80%+ code coverage with fast, deterministic tests that catch regressions early. Tests serve as living documentation and enable confident refactoring.
Rules
- Test Behavior, Not Implementation: Focus on what code does (inputs/outputs), not how it does it
- 80% Coverage Minimum: Maintain 80%+ line coverage, 70%+ branch coverage
- Fast Tests: Unit tests < 100ms, integration tests < 1s, full suite < 5min
- Deterministic Tests: Tests must pass/fail consistently - no flakiness, no randomness
- AAA Pattern: Arrange (setup), Act (execute), Assert (verify) - clear test structure
- One Assertion Per Test: Test one behavior per test case (exceptions for related assertions)
- Mock External Dependencies: Mock European Parliament API, file system, network calls
- Test Edge Cases: Empty inputs, null values, boundary conditions, error paths
- Describe What, Not How: Test names describe expected behavior, not implementation
- Use Type-Safe Mocks: TypeScript mocks should match actual types
- Test Async Properly: Use async/await, avoid callback hell, handle Promise rejections
- Test Security Controls: Validate input validation, rate limiting, authentication, authorization
- Setup and Teardown: Clean up resources, reset mocks, avoid test pollution
- Snapshot Tests Sparingly: Only for stable data structures, not for debugging
- CI/CD Integration: All tests must pass before merge, run on every commit
Examples
✅ Good Pattern: Well-Structured Unit Test
import { describe, it, expect } from 'vitest';
import { InputValidationService, ValidationError } from './InputValidationService';
describe('InputValidationService', () => {
describe('validateSearchQuery', () => {
it('should accept valid search query with all parameters', () => {
const validator = new InputValidationService();
const params = {
keywords: 'climate change',
documentType: 'REPORT',
dateFrom: '2024-01-01',
dateTo: '2024-12-31',
limit: 20,
};
const result = validator.validateSearchQuery(params);
expect(result).toEqual({
keywords: 'climate change',
documentType: 'REPORT',
dateFrom: ,
: ,
: ,
});
});
(, {
validator = ();
params = {
: ,
};
result = validator.(params);
(result.).();
(result.).();
(result.).();
(result.).();
(result.).();
});
(, {
validator = ();
params = {
: ,
};
result = validator.(params);
(result.).();
});
(, {
validator = ();
params = {
: ,
};
( validator.(params))
.();
( validator.(params))
.();
});
(, {
validator = ();
params = {
: .(),
};
( validator.(params))
.();
( validator.(params))
.();
});
(, {
validator = ();
params = {
: ,
};
( validator.(params))
.();
( validator.(params))
.();
});
(, {
validator = ();
params = {
: ,
: ,
};
( validator.(params))
.();
( validator.(params))
.();
});
(, {
validator = ();
params = {
: ,
: ,
};
( validator.(params))
.();
( validator.(params))
.();
});
(, {
validator = ();
params = {
: ,
: ,
};
( validator.(params))
.();
( validator.(params))
.();
});
(, {
validator = ();
( validator.())
.();
( validator.())
.();
( validator.())
.();
});
});
});
✅ Good Pattern: Mocking European Parliament API
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { SearchHandler } from './SearchHandler';
import * as europeanParliamentApi from './europeanParliamentApi';
vi.mock('./europeanParliamentApi');
describe('SearchHandler', () => {
let handler: SearchHandler;
beforeEach(() => {
vi.clearAllMocks();
handler = new SearchHandler();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('handleSearch', () => {
it('should return search results from European Parliament API', async () => {
const mockResults = {
total: 42,
documents: [
{
: ,
: ,
: ,
: ,
},
{
: ,
: ,
: ,
: ,
},
],
};
vi.(europeanParliamentApi.).(mockResults);
request = {
: ,
: {
: ,
: {
: ,
: ,
},
},
};
response = handler.(request);
(europeanParliamentApi.).();
(europeanParliamentApi.).({
: ,
: ,
});
(response.[].).();
(response.[].).();
});
(, () => {
mockResults = {
: ,
: [{ : , : }],
};
vi.(europeanParliamentApi.).(mockResults);
request = {
: ,
: {
: ,
: {
: ,
},
},
};
handler.(request);
handler.(request);
(europeanParliamentApi.).();
});
(, () => {
apiError = ();
vi.(europeanParliamentApi.).(apiError);
request = {
: ,
: {
: ,
: {
: ,
},
},
};
(handler.(request))
.
.();
(europeanParliamentApi.).();
});
(, () => {
request = {
: ,
: {
: ,
: {
: ,
},
},
};
(handler.(request))
.
.();
(europeanParliamentApi.)..();
});
(, () => {
vi.(europeanParliamentApi.).(
(
( ( ()), )
)
);
request = {
: ,
: {
: ,
: {
: ,
},
},
};
(handler.(request))
.
.();
});
});
});
✅ Good Pattern: Testing Async Operations
import { describe, it, expect, vi } from 'vitest';
import { fetchDocumentsBatch } from './batchProcessor';
import * as europeanParliamentApi from './europeanParliamentApi';
vi.mock('./europeanParliamentApi');
describe('fetchDocumentsBatch', () => {
it('should fetch multiple documents concurrently', async () => {
const documentIds = ['EP-001', 'EP-002', 'EP-003'];
vi.mocked(europeanParliamentApi.getDocument).mockImplementation(
async (id: string) => ({
id,
title: `Document ${id}`,
content: 'Test content',
})
);
const startTime = Date.now();
const results = await fetchDocumentsBatch(documentIds);
const duration = Date.now() - startTime;
(results.).();
(results.()?.).();
(results.()?.).();
(results.()?.).();
(duration).();
(europeanParliamentApi.).();
});
(, () => {
documentIds = [, , ];
vi.(europeanParliamentApi.).(
(: ) => {
(id === ) {
();
}
{
id,
: ,
: ,
};
}
);
results = (documentIds);
(results.).();
(results.()).();
(results.()).();
(results.()).();
});
(, () => {
documentIds = .({ : }, );
concurrentCalls = ;
maxConcurrentCalls = ;
vi.(europeanParliamentApi.).(
(: ) => {
concurrentCalls++;
maxConcurrentCalls = .(maxConcurrentCalls, concurrentCalls);
( (resolve, ));
concurrentCalls--;
{
id,
: ,
: ,
};
}
);
(documentIds, { : });
(maxConcurrentCalls).();
});
(, () => {
documentIds = [, ];
vi.(europeanParliamentApi.).(
(: ) => {
(id === ) {
( (resolve, ));
}
{
id,
: ,
: ,
};
}
);
results = (documentIds, { : });
(results.).();
(results.()).();
(results.()).();
});
});
✅ Good Pattern: Testing Security Controls
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { RateLimiter, RateLimitError } from './RateLimiter';
describe('RateLimiter', () => {
let rateLimiter: RateLimiter;
beforeEach(() => {
rateLimiter = new RateLimiter();
});
it('should allow requests under burst limit', async () => {
const clientId = 'test-client-1';
for (let i = 0; i < 10; i++) {
await expect(rateLimiter.checkLimit(clientId))
.resolves
.not.toThrow();
}
});
it('should block requests exceeding burst limit', async () => {
const clientId = 'test-client-2';
( i = ; i < ; i++) {
rateLimiter.(clientId);
}
(rateLimiter.(clientId))
.
.();
});
(, () => {
clientId = ;
( i = ; i < ; i++) {
rateLimiter.(clientId);
( (resolve, ));
}
(rateLimiter.(clientId))
.
.();
});
(, () => {
clientId = ;
( i = ; i < ; i++) {
rateLimiter.(clientId);
}
{
rateLimiter.(clientId);
();
} (error) {
(error).();
((error ).).();
((error ).).();
}
});
(, () => {
client1 = ;
client2 = ;
( i = ; i < ; i++) {
rateLimiter.(client1);
}
(rateLimiter.(client1))
.
.();
(rateLimiter.(client2))
.
..();
});
(, () => {
vi.();
clientId = ;
( i = ; i < ; i++) {
rateLimiter.(clientId);
}
(rateLimiter.(clientId))
.
.();
vi.();
(rateLimiter.(clientId))
.
..();
vi.();
});
});
✅ Good Pattern: Test Fixtures and Factories
export function createTestDocument(
overrides?: Partial<Document>
): Document {
return {
id: overrides?.id ?? 'EP-20240101-00001',
title: overrides?.title ?? 'Test Document',
type: overrides?.type ?? 'REPORT',
date: overrides?.date ?? '2024-01-01',
author: overrides?.author ?? 'Committee on Environment',
language: overrides?.language ?? 'en',
content: overrides?.content ?? 'Test content',
...overrides,
};
}
export function createTestSearchResult(
overrides?: Partial<SearchResult>
): SearchResult {
return {
total: overrides?.total ?? 10,
documents: overrides?.documents ?? [
({ : }),
({ : }),
({ : }),
],
: overrides?. ?? ,
: overrides?. ?? ,
: overrides?. ?? ,
...overrides,
};
}
(): {
{
: ,
: {
: tool,
: args,
},
};
}
(, {
(, () => {
mockResults = ({
: ,
: [
({ : , : }),
({ : , : }),
],
});
request = (, {
: ,
});
});
});
❌ Bad Pattern: Testing Implementation Details
describe('SearchHandler', () => {
it('should call internal validateInput method', async () => {
const handler = new SearchHandler();
const spy = vi.spyOn(handler as any, 'validateInput');
await handler.handleSearch(request);
expect(spy).toHaveBeenCalled();
});
});
describe('SearchHandler', () => {
it('should call the mock correctly', async () => {
const mockFn = vi.fn();
mockFn('test');
expect(mockFn).toHaveBeenCalledWith('test');
});
});
❌ Bad Pattern: Flaky Tests with Timing Issues
it('should process request quickly', async () => {
const start = Date.now();
await processRequest();
const duration = Date.now() - start;
expect(duration).toBeLessThan(100);
});
it('should debounce requests', async () => {
handler.handleRequest(request1);
setTimeout(() => {
handler.handleRequest(request2);
}, 50);
});
❌ Bad Pattern: Not Cleaning Up
describe('Cache', () => {
const cache = new Cache();
it('should cache results', () => {
cache.set('key', 'value');
expect(cache.get('key')).toBe('value');
});
it('should return undefined for missing keys', () => {
expect(cache.get('key')).toBeUndefined();
});
});
it('should use mocked API', async () => {
vi.spyOn(api, 'fetch').mockResolvedValue(mockData);
await handler.process();
});
❌ Bad Pattern: Unclear Test Names
it('works', () => { });
it('test 1', () => { });
it('should return true', () => { });
it('should return search results when keywords are valid', () => { });
it('should throw ValidationError when keywords exceed 200 characters', () => { });
it('should cache results for 5 minutes', () => { });
References
Testing Frameworks
Mocking
Coverage
Best Practices
ISMS Policies
Primary:
Supporting:
Remember
- Test behavior, not implementation: Focus on inputs/outputs, not internal logic
- 80% coverage minimum: Maintain high coverage for confidence in changes
- Fast tests: Unit < 100ms, integration < 1s, suite < 5min
- Deterministic: No flakiness, randomness, or race conditions
- AAA pattern: Arrange, Act, Assert - clear structure
- Mock external dependencies: European Parliament API, file system, network
- Test edge cases: Empty, null, boundaries, errors
- Clean up: Reset mocks, clear state, avoid test pollution
- Type-safe mocks: Use TypeScript for mock type safety
- Async properly: Use async/await, handle Promise rejections
- Security controls: Test validation, rate limiting, authentication
- Descriptive names: Test names describe expected behavior clearly
- CI/CD: All tests pass before merge, run on every commit
- Living documentation: Tests serve as examples of correct usage
- Refactoring confidence: Good tests enable safe refactoring