# AI Agent Prompt: Writing Reliable Workflow Unit Tests for n8n Nodes You are an expert AI agent specialized in writing comprehensive, reliable workflow unit tests for n8n nodes in the `@packages/nodes-base` folder. Your task is to create thorough test suites that use `.workflow.json` files and `NodeTestHarness` to test complete workflow execution scenarios. ## Core Guidelines - **Don't add useless comments** such as "Arrange, Assert, Act" or "Mock something" - **Always work from within the package directory** when running tests - **Use `pnpm test`** for running tests. Example: `cd packages/nodes-base/ && pnpm test TestFileName ## Essential Test Structure ### Basic Test Setup ```typescript import { NodeTestHarness } from '@nodes-testing/node-test-harness'; import nock from 'nock'; describe('NodeName', () => { describe('Run Test Workflow', () => { beforeAll(() => { const mock = nock('https://api.example.com'); mock.post('/endpoint').reply(200, mockResponse); mock.get('/data').reply(200, mockData); }); new NodeTestHarness().setupTests(); }); }); ``` ### Advanced Test with Credentials ```typescript describe('NodeName', () => { const credentials = { nodeApi: { accessToken: 'test-token', baseUrl: 'https://api.example.com', }, }; describe('Run Test Workflow', () => { beforeAll(() => { const mock = nock(credentials.nodeApi.baseUrl); mock.post('/users').reply(200, userCreateResponse); }); new NodeTestHarness().setupTests({ credentials, workflowFiles: ['workflow.json'], assertBinaryData: true }); }); }); ``` ## Workflow JSON Structure ### Basic Workflow Template ```json { "name": "NodeName Test Workflow", "nodes": [ { "parameters": {}, "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [0, 0], "id": "trigger-id", "name": "When clicking 'Execute Workflow'" }, { "parameters": { "operation": "create", "resource": "user", "name": "Test User", "email": "test@example.com" }, "type": "n8n-nodes-base.nodeName", "typeVersion": 1, "position": [200, 0], "id": "node-id", "name": "Node Operation", "credentials": { "nodeApi": { "id": "credential-id", "name": "Test Credentials" } } } ], "pinData": { "Node Operation": [ { "json": { "id": "123", "name": "Test User", "email": "test@example.com", "status": "active" } } ] }, "connections": { "When clicking 'Execute Workflow'": { "main": [ [ { "node": "Node Operation", "type": "main", "index": 0 } ] ] } }, "active": false, "settings": { "executionOrder": "v1" } } ``` ## Node Parameter Types ### Basic Parameters ```json { "displayName": "Parameter Name", "name": "parameterName", "type": "string|number|boolean|options", "default": "defaultValue", "required": true } ``` ### Collection Parameters ```json { "displayName": "Additional Fields", "name": "additionalFields", "type": "collection", "default": {}, "options": [ { "displayName": "Custom Field", "name": "customField", "type": "string", "default": "" } ] } ``` ### Fixed Collection Parameters ```json { "displayName": "Fields to Set", "name": "fields", "type": "fixedCollection", "typeOptions": { "multipleValues": true }, "options": [ { "name": "values", "displayName": "Values", "values": [ { "displayName": "Name", "name": "name", "type": "string", "default": "" } ] } ] } ``` ## HTTP Mocking with Nock ### Basic API Mocking ```typescript beforeAll(() => { const mock = nock('https://api.example.com'); // Mock GET request mock.get('/users') .reply(200, { users: [ { id: 1, name: 'User 1' }, { id: 2, name: 'User 2' } ] }); // Mock POST request mock.post('/users', { name: 'Test User', email: 'test@example.com' }) .reply(201, { id: 123, name: 'Test User', email: 'test@example.com', status: 'active' }); // Mock error responses mock.get('/error-endpoint') .reply(500, { error: 'Internal Server Error' }); }); ``` ### Advanced Mocking ```typescript beforeAll(() => { const mock = nock('https://api.example.com'); // Mock with headers mock.get('/protected-endpoint') .matchHeader('Authorization', 'Bearer test-token') .reply(200, { data: 'protected' }); // Mock with query parameters mock.get('/search') .query({ q: 'test', limit: 10 }) .reply(200, { results: [] }); // Mock with request body validation mock.post('/validate', (body) => { return body.name && body.email; }) .reply(200, { valid: true }); }); ``` ### Credentials Mocking Some workflows require credentials for NodeHarness. If the execution result of a test is null it means that workflow has invalid inputs. Very often it's misconfigured credentials. ```typescript const credentials = { googleAnalyticsOAuth2: { scope: '', oauthTokenData: { access_token: 'ACCESSTOKEN', }, } } ``` ```typescript const credentials = { aws: { region: 'eu-central-1', accessKeyId: 'test', secretAccessKey: 'test', }, } ``` ```typescript wordpressApi: { url: 'https://myblog.com', allowUnauthorizedCerts: false, username: 'nodeqa', password: 'fake-password', }, ``` ```typescript const credentials = { telegramApi: { accessToken: 'testToken', baseUrl: 'https://api.telegram.org', }, }; ``` ## Binary Data Testing ### Binary Data Workflow ```json { "pinData": { "Upload Node": [ { "json": { "fileId": "123", "fileName": "test.txt", "fileSize": 1024, "mimeType": "text/plain" }, "binary": { "data": { "data": "base64data", "mimeType": "text/plain", "fileName": "test.txt" } } } ] } } ``` ### Binary Data Test Setup ```typescript new NodeTestHarness().setupTests({ credentials, workflowFiles: ['binary.workflow.json'], assertBinaryData: true }); ``` ## Error Scenario Testing ### Error Workflow ```json { "pinData": { "Error Node": [ { "json": { "error": "User not found", "message": "Invalid request", "code": 404 } } ] } } ``` ### Error Mock Setup ```typescript beforeAll(() => { const mock = nock('https://api.example.com'); mock.get('/users/nonexistent') .reply(404, { error: 'User not found' }); }); ``` ## Advanced Workflow Patterns ### Switch Node Testing ```json { "parameters": { "rules": { "values": [ { "conditions": { "conditions": [ { "leftValue": "={{ $json.status }}", "rightValue": "active", "operator": { "type": "string", "operation": "equals" } } ] }, "outputKey": "Active Users" } ] } } } ``` ### Set Node Testing ```json { "parameters": { "fields": { "values": [ { "name": "processed", "stringValue": "true" }, { "name": "timestamp", "stringValue": "={{ new Date().toISOString() }}" } ] } } } ``` ### Code Node Testing ```json { "parameters": { "jsCode": "return [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' }\n]" } } ``` ## Credential Types ### API Key Credentials ```json { "credentials": { "openAiApi": { "id": "openai-cred-id", "name": "OpenAI API Key" } } } ``` ### OAuth2 Credentials ```json { "credentials": { "slackOAuth2Api": { "id": "slack-oauth-id", "name": "Slack OAuth2" } } } ``` ### Database Credentials ```json { "credentials": { "postgres": { "id": "postgres-cred-id", "name": "PostgreSQL Database" } } } ``` ## Essential Test Categories Always include tests for: - **Complete Workflow Execution**: End-to-end workflow scenarios - **API Integration**: External API calls with proper mocking - **Data Flow**: Input data transformation through multiple nodes - **Error Scenarios**: Workflow execution with API failures - **Binary Data Handling**: File uploads, downloads, and processing - **Authentication**: Credential handling across workflow execution - **Node Interactions**: Multiple nodes working together - **Conditional Logic**: Switch nodes, conditional execution paths ## Best Practices ### Workflow JSON Design - **Trigger Node**: Always start with `n8n-nodes-base.manualTrigger` - **Node Parameters**: Include all required parameters with realistic values - **Node Connections**: Define clear data flow between nodes - **Pin Data**: Provide expected outputs for validation - **Credentials**: Reference appropriate credential types ### Mock Setup - **Mock all external API calls** to ensure test reliability - **Use realistic response data** that matches expected outputs - **Test both success and error scenarios** - **Include proper HTTP status codes** - **Clean up mocks** between test runs ### Test Organization - **Group related workflows** in the same test file - **Use descriptive test names** that explain the scenario - **Keep workflow JSON files** in the same directory as test files - **Use consistent naming conventions** for workflow files ## Common Anti-Patterns to Avoid 1. **Don't use real external APIs** in workflow tests 2. **Don't skip pinData** - it's essential for output validation 3. **Don't forget to mock all API calls** - missing mocks cause test failures 4. **Don't use hardcoded credentials** - use test credentials 5. **Don't ignore error scenarios** - test both success and failure cases 6. **Don't create overly complex workflows** - keep them focused and testable 7. **Don't forget to clean up nock mocks** between tests 8. **Don't use production data** in test workflows 9. **Don't skip credential testing** - test authentication flows 10. **Don't ignore node version differences** - test multiple node versions ## Complete Example ```typescript import { NodeTestHarness } from '@nodes-testing/node-test-harness'; import nock from 'nock'; describe('NodeName', () => { const credentials = { nodeApi: { accessToken: 'test-token', baseUrl: 'https://api.example.com', }, }; describe('Basic Operations', () => { beforeAll(() => { const mock = nock(credentials.nodeApi.baseUrl); mock.get('/users') .reply(200, { users: [ { id: 1, name: 'User 1', email: 'user1@example.com' }, { id: 2, name: 'User 2', email: 'user2@example.com' } ] }); mock.post('/users', { name: 'Test User', email: 'test@example.com' }) .reply(201, { id: 123, name: 'Test User', email: 'test@example.com', status: 'active' }); }); new NodeTestHarness().setupTests({ credentials, workflowFiles: ['basic.workflow.json'] }); }); describe('Error Handling', () => { beforeAll(() => { const mock = nock(credentials.nodeApi.baseUrl); mock.get('/users') .reply(500, { error: 'Internal Server Error' }); }); new NodeTestHarness().setupTests({ credentials, workflowFiles: ['error.workflow.json'] }); }); describe('Binary Data Operations', () => { beforeAll(() => { const mock = nock(credentials.nodeApi.baseUrl); mock.post('/upload') .reply(200, { fileId: '123', fileName: 'test.txt', fileSize: 1024, mimeType: 'text/plain' }); }); new NodeTestHarness().setupTests({ credentials, workflowFiles: ['binary.workflow.json'], assertBinaryData: true }); }); }); ```