13 KiB
13 KiB
n8n-MCP Testing Architecture
Executive Summary
This document outlines a comprehensive testing strategy for the n8n-MCP project, designed to improve from the current 2.45% coverage to a target of 80%+ coverage while ensuring reliability, maintainability, and performance.
Current State Analysis
Problems Identified
- Low Coverage: 2.45% overall coverage
- Failing Tests: HTTP server authentication tests failing
- No CI/CD: No automated testing pipeline
- Mixed Test Types: Tests scattered without clear organization
- No Mocking Strategy: Direct dependencies on SQLite, n8n packages
- No Performance Testing: No benchmarks for critical operations
Testing Framework Strategy
Primary Framework: Vitest (Replacing Jest)
Rationale for Vitest over Jest:
- Speed: 10-100x faster for large test suites
- Native ESM Support: Better alignment with modern TypeScript
- Built-in Mocking: Superior mocking capabilities
- Watch Mode: Instant feedback during development
- Compatibility: Jest-compatible API for easy migration
- Type Safety: Better TypeScript integration
Supporting Frameworks
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [tsconfigPaths()],
test: {
globals: true,
environment: 'node',
setupFiles: ['./tests/setup/global-setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html', 'lcov'],
exclude: [
'node_modules/**',
'dist/**',
'**/*.d.ts',
'**/*.test.ts',
'**/*.spec.ts',
'tests/**',
'scripts/**'
],
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80
}
},
pool: 'threads',
poolOptions: {
threads: {
singleThread: true
}
},
testTimeout: 30000,
hookTimeout: 30000
}
});
Directory Structure
tests/
├── unit/ # Unit tests (70% of tests)
│ ├── loaders/
│ │ ├── node-loader.test.ts
│ │ └── __mocks__/
│ │ └── n8n-nodes-base.ts
│ ├── parsers/
│ │ ├── node-parser.test.ts
│ │ └── property-extractor.test.ts
│ ├── services/
│ │ ├── property-filter.test.ts
│ │ ├── config-validator.test.ts
│ │ └── workflow-validator.test.ts
│ └── database/
│ ├── node-repository.test.ts
│ └── __mocks__/
│ └── better-sqlite3.ts
├── integration/ # Integration tests (20%)
│ ├── mcp/
│ │ ├── server.test.ts
│ │ └── tools.test.ts
│ ├── n8n-api/
│ │ ├── workflow-crud.test.ts
│ │ └── webhook-trigger.test.ts
│ └── database/
│ ├── sqlite-operations.test.ts
│ └── fts5-search.test.ts
├── e2e/ # End-to-end tests (10%)
│ ├── workflows/
│ │ ├── complete-workflow.test.ts
│ │ └── ai-agent-workflow.test.ts
│ └── mcp-protocol/
│ └── full-session.test.ts
├── performance/ # Performance benchmarks
│ ├── node-loading.bench.ts
│ ├── search.bench.ts
│ └── validation.bench.ts
├── fixtures/ # Test data
│ ├── nodes/
│ │ ├── http-request.json
│ │ └── slack.json
│ ├── workflows/
│ │ ├── simple.json
│ │ └── complex-ai.json
│ └── factories/
│ ├── node.factory.ts
│ └── workflow.factory.ts
├── setup/ # Test configuration
│ ├── global-setup.ts
│ ├── test-containers.ts
│ └── test-database.ts
└── utils/ # Test utilities
├── mocks/
│ ├── mcp-sdk.ts
│ └── express.ts
├── builders/
│ ├── node.builder.ts
│ └── workflow.builder.ts
└── helpers/
├── async.ts
└── assertions.ts
Mock Strategy
1. Database Mocking
// tests/unit/database/__mocks__/better-sqlite3.ts
import { vi } from 'vitest';
export class Database {
private data = new Map<string, any[]>();
prepare = vi.fn((sql: string) => ({
all: vi.fn(() => this.data.get('nodes') || []),
get: vi.fn((params) => this.data.get('nodes')?.find(n => n.id === params.id)),
run: vi.fn(),
finalize: vi.fn()
}));
exec = vi.fn();
close = vi.fn();
// Test helper to set mock data
setMockData(table: string, data: any[]) {
this.data.set(table, data);
}
}
export default vi.fn(() => new Database());
2. n8n Package Mocking
// tests/unit/loaders/__mocks__/n8n-nodes-base.ts
import { vi } from 'vitest';
import { readFileSync } from 'fs';
import { join } from 'path';
// Load real node definitions for testing
const mockNodes = JSON.parse(
readFileSync(join(__dirname, '../../fixtures/nodes/mock-nodes.json'), 'utf8')
);
export const loadClassInIsolation = vi.fn((filePath: string) => {
const nodeName = filePath.split('/').pop()?.replace('.node.js', '');
return mockNodes[nodeName] || { description: { properties: [] } };
});
export const NodeHelpers = {
getVersionedNodeTypeAll: vi.fn(() => [])
};
3. External API Mocking
// tests/utils/mocks/axios.ts
import { vi } from 'vitest';
import type { AxiosRequestConfig } from 'axios';
export const createAxiosMock = () => {
const mock = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
create: vi.fn(() => mock)
};
// Default responses
mock.get.mockResolvedValue({ data: { success: true } });
mock.post.mockResolvedValue({ data: { id: '123' } });
return mock;
};
Test Data Management
1. Factory Pattern
// tests/fixtures/factories/node.factory.ts
import { Factory } from 'fishery';
import type { INodeType } from 'n8n-workflow';
export const nodeFactory = Factory.define<INodeType>(({ sequence }) => ({
name: `TestNode${sequence}`,
displayName: `Test Node ${sequence}`,
group: ['test'],
version: 1,
description: 'Test node for unit tests',
defaults: {
name: `Test Node ${sequence}`,
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Test Property',
name: 'testProp',
type: 'string',
default: '',
}
]
}));
// Usage in tests:
const httpNode = nodeFactory.build({
name: 'HttpRequest',
properties: [/* custom properties */]
});
2. Builder Pattern
// tests/utils/builders/workflow.builder.ts
export class WorkflowBuilder {
private workflow = {
name: 'Test Workflow',
nodes: [],
connections: {},
settings: {}
};
withName(name: string) {
this.workflow.name = name;
return this;
}
addNode(node: any) {
this.workflow.nodes.push(node);
return this;
}
connect(from: string, to: string) {
this.workflow.connections[from] = {
main: [[{ node: to, type: 'main', index: 0 }]]
};
return this;
}
build() {
return this.workflow;
}
}
CI/CD Pipeline (GitHub Actions)
# .github/workflows/test.yml
name: Test Suite
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
test-suite: [unit, integration, e2e]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Run ${{ matrix.test-suite }} tests
run: npm run test:${{ matrix.test-suite }}
env:
CI: true
- name: Upload coverage
if: matrix.test-suite == 'unit' && matrix.node-version == '20.x'
uses: codecov/codecov-action@v4
with:
file: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
performance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Install dependencies
run: npm ci
- name: Run benchmarks
run: npm run bench
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
tool: 'vitest'
output-file-path: bench-results.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run typecheck
- name: Check coverage thresholds
run: npm run test:coverage:check
Testing Phases and Priorities
Phase 1: Foundation (Weeks 1-2)
- Setup Vitest and migrate existing tests
- Create mock infrastructure for SQLite and n8n packages
- Setup CI/CD pipeline with basic checks
- Target: 20% coverage
Phase 2: Core Unit Tests (Weeks 3-4)
- Test critical services: validators, parsers, loaders
- Database layer with full mocking
- MCP tools unit tests
- Target: 50% coverage
Phase 3: Integration Tests (Weeks 5-6)
- MCP protocol integration tests
- n8n API integration with test containers
- Database operations with real SQLite
- Target: 70% coverage
Phase 4: E2E & Performance (Weeks 7-8)
- Complete workflow scenarios
- Performance benchmarks for critical paths
- Error handling scenarios
- Target: 80%+ coverage
Performance Testing
// tests/performance/node-loading.bench.ts
import { bench, describe } from 'vitest';
import { NodeLoader } from '@/loaders/node-loader';
describe('Node Loading Performance', () => {
bench('Load single node', async () => {
const loader = new NodeLoader();
await loader.loadNode('n8n-nodes-base.httpRequest');
});
bench('Load all nodes', async () => {
const loader = new NodeLoader();
await loader.loadAllNodes();
}, {
iterations: 10,
time: 5000 // 5 second time budget
});
bench('Parse complex node', async () => {
const parser = new NodeParser();
await parser.parseNode(complexNodeFixture);
});
});
Error Testing Strategy
// tests/unit/services/error-scenarios.test.ts
describe('Error Handling', () => {
it('should handle network failures gracefully', async () => {
const api = new N8nAPIClient();
mockAxios.get.mockRejectedValue(new Error('Network error'));
await expect(api.getWorkflow('123'))
.rejects.toThrow('Failed to fetch workflow');
expect(logger.error).toHaveBeenCalledWith(
'Network error while fetching workflow',
expect.any(Error)
);
});
it('should handle malformed data', () => {
const validator = new ConfigValidator();
const malformed = { nodes: 'not-an-array' };
const result = validator.validate(malformed);
expect(result.isValid).toBe(false);
expect(result.errors).toContain('nodes must be an array');
});
});
Coverage Enforcement
// package.json scripts
{
"scripts": {
"test": "vitest",
"test:unit": "vitest run tests/unit",
"test:integration": "vitest run tests/integration",
"test:e2e": "vitest run tests/e2e",
"test:coverage": "vitest run --coverage",
"test:coverage:check": "vitest run --coverage --coverage.thresholdAutoUpdate=false",
"test:watch": "vitest watch",
"bench": "vitest bench",
"bench:compare": "vitest bench --compare"
}
}
Monitoring and Reporting
1. Coverage Badges


2. Performance Tracking
- Automated benchmark comparisons on PRs
- Performance regression alerts
- Historical performance graphs
3. Test Reports
- HTML coverage reports
- Failed test summaries in PRs
- Flaky test detection
Migration Path from Current State
- Week 1: Setup Vitest, migrate existing tests
- Week 2: Create mock infrastructure
- Week 3-4: Write unit tests for critical paths
- Week 5-6: Add integration tests
- Week 7-8: E2E tests and performance benchmarks
Success Metrics
- Coverage: 80%+ overall, 90%+ for critical paths
- Performance: All operations under 100ms
- Reliability: Zero flaky tests
- CI Time: Full suite under 5 minutes
- Developer Experience: Tests run in <1 second locally