원클릭으로
testing-mcp-tools
Testing patterns for MCP tools, integration tests, mocking European Parliament API, and achieving 80%+ coverage
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Testing patterns for MCP tools, integration tests, mocking European Parliament API, and achieving 80%+ coverage
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
C4 architecture model, security architecture, Mermaid diagrams, SECURITY_ARCHITECTURE.md, and comprehensive documentation per Hack23 Secure Development Policy
AI-augmented development controls, GitHub Copilot governance, LLM security, AI-generated code review per Hack23 Secure Development Policy
EU AI Act compliance, OWASP LLM security, responsible AI practices for parliamentary data and MCP server applications
Enforce code quality with ESLint, TypeScript strict mode, Knip unused detection, and quality gates for MCP servers
ISO 27001, NIST CSF 2.0, CIS Controls v8.1, EU CRA compliance mapping, multi-standard alignment per Hack23 ISMS policies
Contribution process with PR workflow, code review standards, commit conventions, and open source best practices
| name | testing-mcp-tools |
| description | Testing patterns for MCP tools, integration tests, mocking European Parliament API, and achieving 80%+ coverage |
| license | MIT |
This skill applies when:
This project uses Vitest as the test framework with a coverage target of 80% line coverage and 70% branch coverage.
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { handleSearchMEPs } from './search-meps';
describe('MCP Tool: search_meps', () => {
beforeEach(() => {
// Reset mocks before each test
vi.clearAllMocks();
});
it('should validate input schema', async () => {
const invalidRequest = {
params: {
name: 'search_meps',
arguments: {
country: 'USA', // Invalid: not EU country
limit: 200, // Invalid: exceeds max (100)
},
},
};
await expect(handleSearchMEPs(invalidRequest)).rejects.toThrow();
});
it('should return MCP-compliant response structure', async () => {
// Mock API
vi.mock('./api', () => ({
searchMEPs: vi.fn().mockResolvedValue([
{ id: 1, fullName: 'Test MEP', country: 'DE' },
]),
}));
const validRequest = {
params: {
name: 'search_meps',
arguments: {
country: 'DE',
limit: 10,
},
},
};
const response = await handleSearchMEPs(validRequest);
// Verify MCP response structure
expect(response).toHaveProperty('content');
expect(Array.isArray(response.content)).toBe(true);
expect(response.content[0]).toHaveProperty('type', 'text');
expect(response.content[0]).toHaveProperty('text');
// Verify response content
const data = JSON.parse(response.content[0].text);
expect(data).toHaveProperty('count');
expect(data).toHaveProperty('meps');
expect(Array.isArray(data.meps)).toBe(true);
});
it('should handle API errors gracefully', async () => {
// Mock API failure
vi.mock('./api', () => ({
searchMEPs: vi.fn().mockRejectedValue(new Error('API Error')),
}));
const request = {
params: {
name: 'search_meps',
arguments: { country: 'DE' },
},
};
await expect(handleSearchMEPs(request)).rejects.toThrow('Failed to search MEPs');
// Verify internal error is NOT exposed
});
it('should reject invalid characters in country code', async () => {
const request = {
params: {
name: 'search_meps',
arguments: {
country: '<script>alert("xss")</script>',
},
},
};
await expect(handleSearchMEPs(request)).rejects.toThrow();
});
it('should apply default values for optional parameters', async () => {
vi.mock('./api', () => ({
searchMEPs: vi.fn().mockResolvedValue([]),
}));
const request = {
params: {
name: 'search_meps',
arguments: {
country: 'DE',
// limit not provided - should default to 20
},
},
};
await handleSearchMEPs(request);
const apiMock = await import('./api');
expect(apiMock.searchMEPs).toHaveBeenCalledWith(
expect.objectContaining({ limit: 20 })
);
});
});
import { vi } from 'vitest';
/**
* Mock European Parliament API responses
*/
export function mockEPAPI() {
// Mock fetch globally
global.fetch = vi.fn();
return {
mockMEP(id: number, data: Partial<MEP> = {}) {
(global.fetch as any).mockResolvedValueOnce(
new Response(JSON.stringify({
id,
fullName: 'Test MEP',
country: 'DE',
partyGroup: 'PPE',
active: true,
...data,
}), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
);
},
mockAPIError(status: number = 500) {
(global.fetch as any).mockResolvedValueOnce(
new Response(null, { status })
);
},
mockRateLimit(retryAfter: number = 60) {
(global.fetch as any).mockResolvedValueOnce(
new Response(null, {
status: 429,
headers: { 'Retry-After': retryAfter.toString() },
})
);
},
};
}
// Usage in tests
describe('EP API Integration', () => {
it('should handle rate limiting', async () => {
const api = mockEPAPI();
// First call returns rate limit
api.mockRateLimit(1);
// Second call succeeds
api.mockMEP(12345);
const mep = await fetchMEPWithRetry(12345);
expect(mep.id).toBe(12345);
expect(fetch).toHaveBeenCalledTimes(2);
});
});
import { describe, it, expect } from 'vitest';
import { MEPSchema } from './schemas';
describe('MEPSchema', () => {
it('should validate valid MEP data', () => {
const validMEP = {
id: 12345,
fullName: 'Jane Doe',
country: 'DE',
partyGroup: 'PPE',
active: true,
termStart: '2019-07-02',
committees: [],
};
const result = MEPSchema.parse(validMEP);
expect(result.id).toBe(12345);
});
it('should reject invalid country code', () => {
const invalidMEP = {
id: 12345,
fullName: 'Jane Doe',
country: 'USA', // Not EU country
partyGroup: 'PPE',
active: true,
termStart: '2019-07-02',
};
expect(() => MEPSchema.parse(invalidMEP)).toThrow();
});
it('should apply default values', () => {
const mepWithoutDefaults = {
id: 12345,
fullName: 'Jane Doe',
country: 'DE',
partyGroup: 'PPE',
active: true,
termStart: '2019-07-02',
// committees not provided
};
const result = MEPSchema.parse(mepWithoutDefaults);
expect(result.committees).toEqual([]);
});
it('should transform and normalize data', () => {
const mepWithWhitespace = {
id: 12345,
fullName: ' Jane Doe ',
country: 'DE',
partyGroup: 'PPE',
active: true,
termStart: '2019-07-02',
};
const result = MEPSchema.parse(mepWithWhitespace);
expect(result.fullName).toBe('Jane Doe'); // Trimmed
});
});
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { EuropeanParliamentMCPServer } from './server';
describe('MCP Server Integration', () => {
let server: EuropeanParliamentMCPServer;
beforeAll(async () => {
server = new EuropeanParliamentMCPServer();
await server.start();
});
afterAll(async () => {
await server.shutdown();
});
it('should list available tools', async () => {
const request = {
jsonrpc: '2.0',
id: 1,
method: 'tools/list',
};
const response = await server.handleRequest(request);
expect(response.result).toHaveProperty('tools');
expect(Array.isArray(response.result.tools)).toBe(true);
expect(response.result.tools.length).toBeGreaterThan(0);
});
it('should execute tool and return result', async () => {
const request = {
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: {
name: 'search_meps',
arguments: {
country: 'DE',
limit: 5,
},
},
};
const response = await server.handleRequest(request);
expect(response.result).toHaveProperty('content');
expect(Array.isArray(response.result.content)).toBe(true);
});
});
import { describe, it, expect } from 'vitest';
import { performance } from 'perf_hooks';
describe('Performance Tests', () => {
it('should respond to MEP requests in <200ms', async () => {
const startTime = performance.now();
await getMEP(12345);
const duration = performance.now() - startTime;
expect(duration).toBeLessThan(200);
});
it('should achieve >80% cache hit rate', async () => {
// Warm cache
await getCachedMEP(12345);
// Make 100 requests
for (let i = 0; i < 100; i++) {
await getCachedMEP(12345);
}
const stats = getCacheStats('mep');
const hitRate = stats.hits / (stats.hits + stats.misses);
expect(hitRate).toBeGreaterThan(0.8);
});
});
// NEVER - slow and unreliable!
it('should fetch MEP', async () => {
const mep = await fetch('https://data.europarl.europa.eu/api/v2/meps/12345');
expect(mep).toBeDefined();
});
// NEVER - test doesn't verify anything!
it('should process MEP', async () => {
await processMEP(12345);
// No assertions!
});
// NEVER - test behavior, not implementation!
it('should call internal function', async () => {
const spy = vi.spyOn(internal, 'helperFunction');
await publicAPI();
expect(spy).toHaveBeenCalled(); // Testing internals!
});
Target: 80% line coverage, 70% branch coverage
{
"vitest": {
"coverage": {
"statements": 80,
"branches": 70,
"functions": 80,
"lines": 80
}
}
}
Primary:
Related:
npm run test:licenses)