Skip to main content
Glama

n8n MCP Server

MIT License
133
1,377
  • Apple
  • Linux
testing.md14.5 kB
# Testing This document describes the testing approach for the n8n MCP Server and provides guidelines for writing effective tests. ## Overview The n8n MCP Server uses Jest as its testing framework and follows a multi-level testing approach: 1. **Unit Tests**: Test individual components in isolation 2. **Integration Tests**: Test interactions between components 3. **End-to-End Tests**: Test the entire system as a whole Tests are organized in the `tests/` directory, with a structure that mirrors the `src/` directory. ## Running Tests ### Running All Tests To run all tests: ```bash npm test ``` This command runs all tests and outputs a summary of the results. ### Running Tests with Coverage To run tests with coverage reporting: ```bash npm run test:coverage ``` This generates coverage reports in the `coverage/` directory, including HTML reports that you can view in a browser. ### Running Tests in Watch Mode During development, you can run tests in watch mode, which will automatically rerun tests when files change: ```bash npm run test:watch ``` ### Running Specific Tests To run tests in a specific file or directory: ```bash npx jest path/to/test-file.test.ts ``` Or to run tests matching a specific pattern: ```bash npx jest -t "test pattern" ``` ## Test Structure Tests are organized into the following directories: - `tests/unit/`: Unit tests for individual components - `tests/integration/`: Integration tests that test interactions between components - `tests/e2e/`: End-to-end tests that test the entire system - `tests/mocks/`: Shared test fixtures and mocks ### Unit Tests Unit tests are organized in a structure that mirrors the `src/` directory. For example: - `src/api/n8n-client.ts` has a corresponding test at `tests/unit/api/n8n-client.test.ts` - `src/tools/workflow/list.ts` has a corresponding test at `tests/unit/tools/workflow/list.test.ts` ### Integration Tests Integration tests focus on testing interactions between components, such as: - Testing that tools correctly use the API client - Testing that resources correctly format data from the API ### End-to-End Tests End-to-end tests test the entire system, from the transport layer to the API client and back. ## Writing Effective Tests ### Unit Test Example Here's an example of a unit test for a workflow tool: ```typescript // tests/unit/tools/workflow/list.test.ts import { describe, it, expect, jest } from '@jest/globals'; import { getListWorkflowsToolDefinition, handleListWorkflows } from '../../../../src/tools/workflow/list.js'; import { N8nClient } from '../../../../src/api/n8n-client.js'; // Mock data const mockWorkflows = [ { id: '1234abc', name: 'Test Workflow 1', active: true, createdAt: '2025-03-01T12:00:00.000Z', updatedAt: '2025-03-02T14:30:00.000Z' }, { id: '5678def', name: 'Test Workflow 2', active: false, createdAt: '2025-03-01T12:00:00.000Z', updatedAt: '2025-03-12T10:15:00.000Z' } ]; describe('Workflow List Tool', () => { describe('getListWorkflowsToolDefinition', () => { it('should return the correct tool definition', () => { const definition = getListWorkflowsToolDefinition(); expect(definition.name).toBe('workflow_list'); expect(definition.description).toBeTruthy(); expect(definition.inputSchema).toBeDefined(); expect(definition.inputSchema.properties).toHaveProperty('active'); expect(definition.inputSchema.required).toEqual([]); }); }); describe('handleListWorkflows', () => { it('should return all workflows when no filter is provided', async () => { // Mock the API client const mockClient = { getWorkflows: jest.fn().mockResolvedValue(mockWorkflows) }; const result = await handleListWorkflows(mockClient as unknown as N8nClient, {}); expect(mockClient.getWorkflows).toHaveBeenCalledWith(undefined); expect(result.isError).toBeFalsy(); // Parse the JSON text to check the content const content = JSON.parse(result.content[0].text); expect(content).toHaveLength(2); expect(content[0].id).toBe('1234abc'); expect(content[1].id).toBe('5678def'); }); it('should filter workflows by active status', async () => { // Mock the API client const mockClient = { getWorkflows: jest.fn().mockResolvedValue(mockWorkflows) }; const result = await handleListWorkflows(mockClient as unknown as N8nClient, { active: true }); expect(mockClient.getWorkflows).toHaveBeenCalledWith(true); expect(result.isError).toBeFalsy(); // Parse the JSON text to check the content const content = JSON.parse(result.content[0].text); expect(content).toHaveLength(2); }); it('should handle API errors', async () => { // Mock the API client to throw an error const mockClient = { getWorkflows: jest.fn().mockRejectedValue(new Error('API error')) }; const result = await handleListWorkflows(mockClient as unknown as N8nClient, {}); expect(result.isError).toBeTruthy(); expect(result.content[0].text).toContain('API error'); }); }); }); ``` ### Integration Test Example Here's an example of an integration test that tests the interaction between a resource handler and the API client: ```typescript // tests/integration/resources/static/workflows.test.ts import { describe, it, expect, jest } from '@jest/globals'; import { handleWorkflowsRequest, WORKFLOWS_URI } from '../../../../src/resources/static/workflows.js'; import { N8nClient } from '../../../../src/api/n8n-client.js'; // Mock data const mockWorkflows = [ { id: '1234abc', name: 'Test Workflow 1', active: true, createdAt: '2025-03-01T12:00:00.000Z', updatedAt: '2025-03-02T14:30:00.000Z' }, { id: '5678def', name: 'Test Workflow 2', active: false, createdAt: '2025-03-01T12:00:00.000Z', updatedAt: '2025-03-12T10:15:00.000Z' } ]; describe('Workflows Resource Handler', () => { it('should return a properly formatted response', async () => { // Mock the API client const mockClient = { getWorkflows: jest.fn().mockResolvedValue(mockWorkflows) }; const response = await handleWorkflowsRequest(mockClient as unknown as N8nClient); expect(mockClient.getWorkflows).toHaveBeenCalled(); expect(response.contents).toHaveLength(1); expect(response.contents[0].uri).toBe(WORKFLOWS_URI); expect(response.contents[0].mimeType).toBe('application/json'); // Parse the JSON text to check the content const content = JSON.parse(response.contents[0].text); expect(content).toHaveProperty('workflows'); expect(content.workflows).toHaveLength(2); expect(content.count).toBe(2); expect(content.workflows[0].id).toBe('1234abc'); }); it('should handle API errors', async () => { // Mock the API client to throw an error const mockClient = { getWorkflows: jest.fn().mockRejectedValue(new Error('API error')) }; await expect(handleWorkflowsRequest(mockClient as unknown as N8nClient)) .rejects .toThrow('Failed to retrieve workflows'); }); }); ``` ### End-to-End Test Example Here's an example of an end-to-end test that tests the entire system: ```typescript // tests/e2e/workflow-operations.test.ts import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { MemoryServerTransport } from '@modelcontextprotocol/sdk/server/memory.js'; import { createServer } from '../../src/index.js'; describe('End-to-End Workflow Operations', () => { let server: Server; let transport: MemoryServerTransport; beforeAll(async () => { // Mock the environment process.env.N8N_API_URL = 'http://localhost:5678/api/v1'; process.env.N8N_API_KEY = 'test-api-key'; // Create the server with a memory transport transport = new MemoryServerTransport(); server = await createServer(transport); }); afterAll(async () => { await server.close(); }); it('should list workflows', async () => { // Send a request to list workflows const response = await transport.sendRequest({ jsonrpc: '2.0', id: '1', method: 'callTool', params: { name: 'workflow_list', arguments: {} } }); expect(response.result).toBeDefined(); expect(response.result.content).toHaveLength(1); expect(response.result.content[0].type).toBe('text'); // Parse the JSON text to check the content const content = JSON.parse(response.result.content[0].text); expect(Array.isArray(content)).toBe(true); }); it('should retrieve a workflow by ID', async () => { // Send a request to get a workflow const response = await transport.sendRequest({ jsonrpc: '2.0', id: '2', method: 'callTool', params: { name: 'workflow_get', arguments: { id: '1234abc' } } }); expect(response.result).toBeDefined(); expect(response.result.content).toHaveLength(1); expect(response.result.content[0].type).toBe('text'); // Parse the JSON text to check the content const content = JSON.parse(response.result.content[0].text); expect(content).toHaveProperty('id'); expect(content.id).toBe('1234abc'); }); }); ``` ## Test Fixtures and Mocks To avoid duplication and improve test maintainability, common test fixtures and mocks are stored in the `tests/mocks/` directory. ### Axios Mock The Axios HTTP client is mocked using `axios-mock-adapter` to simulate HTTP responses without making actual API calls: ```typescript // tests/mocks/axios-mock.ts import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; // Create a new instance of the mock adapter export const axiosMock = new MockAdapter(axios); // Helper function to reset the mock adapter before each test export function resetAxiosMock() { axiosMock.reset(); } ``` ### n8n API Fixtures Common fixtures for n8n API responses are stored in a shared file: ```typescript // tests/mocks/n8n-fixtures.ts export const mockWorkflows = [ { id: '1234abc', name: 'Test Workflow 1', active: true, createdAt: '2025-03-01T12:00:00.000Z', updatedAt: '2025-03-02T14:30:00.000Z', nodes: [ { id: 'node1', name: 'Start', type: 'n8n-nodes-base.start', position: [100, 200], parameters: {} } ], connections: {} }, { id: '5678def', name: 'Test Workflow 2', active: false, createdAt: '2025-03-01T12:00:00.000Z', updatedAt: '2025-03-12T10:15:00.000Z', nodes: [], connections: {} } ]; export const mockExecutions = [ { id: 'exec123', workflowId: '1234abc', workflowName: 'Test Workflow 1', status: 'success', startedAt: '2025-03-10T15:00:00.000Z', finishedAt: '2025-03-10T15:01:00.000Z', mode: 'manual' }, { id: 'exec456', workflowId: '1234abc', workflowName: 'Test Workflow 1', status: 'error', startedAt: '2025-03-09T12:00:00.000Z', finishedAt: '2025-03-09T12:00:10.000Z', mode: 'manual' } ]; ``` ## Test Environment The test environment is configured in `jest.config.js` and `babel.config.js`. Key configurations include: - TypeScript support via Babel - ES module support - Coverage reporting The `tests/test-setup.ts` file contains global setup code that runs before tests: ```typescript // tests/test-setup.ts import { jest } from '@jest/globals'; import { resetAxiosMock } from './mocks/axios-mock'; // Reset mocks before each test beforeEach(() => { jest.clearAllMocks(); resetAxiosMock(); }); ``` ## Best Practices ### General Testing Guidelines 1. **Write tests first**: Follow a test-driven development (TDD) approach when possible. 2. **Test behavior, not implementation**: Focus on what a component does, not how it's implemented. 3. **Keep tests simple**: Each test should test one behavior or aspect of functionality. 4. **Use descriptive test names**: Test names should describe the expected behavior. 5. **Follow the AAA pattern**: Arrange, Act, Assert (setup, execute, verify). ### Mocking Best Practices 1. **Mock dependencies, not the unit under test**: Only mock external dependencies, not the code you're testing. 2. **Use the minimum viable mock**: Only mock the methods and behavior needed for the test. 3. **Ensure mock behavior is realistic**: Mocks should behave similarly to the real implementation. 4. **Verify interactions with mocks**: Use `expect(mock).toHaveBeenCalled()` to verify interactions. ### Error Testing Best Practices 1. **Test error cases**: Don't just test the happy path; test error handling too. 2. **Simulate errors with mocks**: Use mocks to simulate error scenarios. 3. **Verify error messages**: Ensure error messages are helpful and descriptive. ### Performance Testing Considerations 1. **Monitor test performance**: Slow tests can slow down development. 2. **Use test timeout values wisely**: Set appropriate timeout values for async tests. 3. **Minimize redundant setup**: Use `beforeEach` and `beforeAll` to avoid redundant setup. ## Continuous Integration Tests are run automatically in CI environments on pull requests and commits to the main branch. The CI configuration ensures tests pass before code can be merged. ### CI Test Requirements - All tests must pass - Test coverage must not decrease - Linting checks must pass ## Debugging Tests ### Console Output You can use `console.log()` statements in your tests to debug issues: ```typescript it('should do something', () => { const result = doSomething(); console.log('Result:', result); expect(result).toBe(expectedValue); }); ``` When running tests with Jest, console output will be displayed for failing tests by default. ### Using the Debugger You can also use the Node.js debugger with Jest: ```bash node --inspect-brk node_modules/.bin/jest --runInBand path/to/test ``` Then connect to the debugger with Chrome DevTools or VS Code. ## Conclusion Thorough testing is essential for maintaining a reliable and robust n8n MCP Server. By following these guidelines and examples, you can write effective tests that help ensure your code works as expected and catches issues early.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/leonardsellem/n8n-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server