mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
12 KiB
12 KiB
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 testfor running tests. Example: `cd packages/nodes-base/ && pnpm test TestFileName
Essential Test Structure
Basic Test Setup
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
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
{
"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
{
"displayName": "Parameter Name",
"name": "parameterName",
"type": "string|number|boolean|options",
"default": "defaultValue",
"required": true
}
Collection Parameters
{
"displayName": "Additional Fields",
"name": "additionalFields",
"type": "collection",
"default": {},
"options": [
{
"displayName": "Custom Field",
"name": "customField",
"type": "string",
"default": ""
}
]
}
Fixed Collection Parameters
{
"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
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
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.
const credentials = {
googleAnalyticsOAuth2: {
scope: '',
oauthTokenData: {
access_token: 'ACCESSTOKEN',
},
}
}
const credentials = {
aws: {
region: 'eu-central-1',
accessKeyId: 'test',
secretAccessKey: 'test',
},
}
wordpressApi: {
url: 'https://myblog.com',
allowUnauthorizedCerts: false,
username: 'nodeqa',
password: 'fake-password',
},
const credentials = {
telegramApi: {
accessToken: 'testToken',
baseUrl: 'https://api.telegram.org',
},
};
Binary Data Testing
Binary Data Workflow
{
"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
new NodeTestHarness().setupTests({
credentials,
workflowFiles: ['binary.workflow.json'],
assertBinaryData: true
});
Error Scenario Testing
Error Workflow
{
"pinData": {
"Error Node": [
{
"json": {
"error": "User not found",
"message": "Invalid request",
"code": 404
}
}
]
}
}
Error Mock Setup
beforeAll(() => {
const mock = nock('https://api.example.com');
mock.get('/users/nonexistent')
.reply(404, { error: 'User not found' });
});
Advanced Workflow Patterns
Switch Node Testing
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.status }}",
"rightValue": "active",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
},
"outputKey": "Active Users"
}
]
}
}
}
Set Node Testing
{
"parameters": {
"fields": {
"values": [
{
"name": "processed",
"stringValue": "true"
},
{
"name": "timestamp",
"stringValue": "={{ new Date().toISOString() }}"
}
]
}
}
}
Code Node Testing
{
"parameters": {
"jsCode": "return [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' }\n]"
}
}
Credential Types
API Key Credentials
{
"credentials": {
"openAiApi": {
"id": "openai-cred-id",
"name": "OpenAI API Key"
}
}
}
OAuth2 Credentials
{
"credentials": {
"slackOAuth2Api": {
"id": "slack-oauth-id",
"name": "Slack OAuth2"
}
}
}
Database Credentials
{
"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
- Don't use real external APIs in workflow tests
- Don't skip pinData - it's essential for output validation
- Don't forget to mock all API calls - missing mocks cause test failures
- Don't use hardcoded credentials - use test credentials
- Don't ignore error scenarios - test both success and failure cases
- Don't create overly complex workflows - keep them focused and testable
- Don't forget to clean up nock mocks between tests
- Don't use production data in test workflows
- Don't skip credential testing - test authentication flows
- Don't ignore node version differences - test multiple node versions
Complete Example
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
});
});
});