Skip to content

Commit c7fe3f4

Browse files
Add @trigger.dev/test package for testing trigger.dev tasks
Co-Authored-By: Eric Allam <[email protected]>
1 parent 558a39c commit c7fe3f4

File tree

13 files changed

+1123
-0
lines changed

13 files changed

+1123
-0
lines changed

packages/test/README.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# @trigger.dev/test
2+
3+
Testing utilities for trigger.dev tasks.
4+
5+
## Installation
6+
7+
```bash
8+
npm install --save-dev @trigger.dev/test
9+
```
10+
11+
## Usage
12+
13+
### Creating a mock task
14+
15+
```typescript
16+
import { mockTask } from "@trigger.dev/test";
17+
18+
const task = mockTask({
19+
id: "test-task",
20+
output: { success: true },
21+
});
22+
23+
// Use the mock task in your tests
24+
const result = await task.triggerAndWait({}).unwrap();
25+
console.log(result); // { success: true }
26+
```
27+
28+
### Unit testing a task with mocked dependencies
29+
30+
```typescript
31+
import { mockTask, testTaskFunction } from "@trigger.dev/test";
32+
33+
// Create a mock for a dependent task
34+
const dependentTask = mockTask({
35+
id: "dependent-task",
36+
output: { result: "mocked-result" },
37+
});
38+
39+
// Test a task that uses the dependent task
40+
const result = await testTaskFunction(mainTask, { input: "test" }, {}, {
41+
mockDependencies: [dependentTask]
42+
});
43+
44+
// Verify the result
45+
expect(result).toEqual({
46+
mainResult: "Processed: mocked-result",
47+
input: "test"
48+
});
49+
```
50+
51+
### Setting up a test environment with mocked tasks
52+
53+
```typescript
54+
import { setupMockTaskEnvironment, mockTask } from "@trigger.dev/test";
55+
56+
// Set up the test environment
57+
const cleanup = setupMockTaskEnvironment((registry) => {
58+
// Register mock tasks
59+
registry.registerMockTask(mockTask({
60+
id: "task-1",
61+
output: { result: "mocked-result-1" },
62+
}));
63+
64+
registry.registerMockTask(mockTask({
65+
id: "task-2",
66+
output: { result: "mocked-result-2" },
67+
}));
68+
});
69+
70+
// Run your tests...
71+
72+
// Clean up
73+
cleanup();
74+
```
75+
76+
### Verifying task triggers
77+
78+
```typescript
79+
import { mockTask, verifyTaskTriggered, getTaskTriggerCount } from "@trigger.dev/test";
80+
81+
const task = mockTask({
82+
id: "test-task",
83+
output: { success: true },
84+
});
85+
86+
// Trigger the task
87+
await task.trigger({ data: "test" });
88+
89+
// Verify the task was triggered
90+
console.log(verifyTaskTriggered(task)); // true
91+
console.log(verifyTaskTriggered(task, { data: "test" })); // true
92+
console.log(getTaskTriggerCount(task)); // 1
93+
```
94+
95+
### Testing hooks
96+
97+
```typescript
98+
import { mockTask, createMockHooks, verifyHookCalled } from "@trigger.dev/test";
99+
100+
const task = mockTask({
101+
id: "test-task",
102+
output: { success: true },
103+
});
104+
105+
const hooks = createMockHooks(task);
106+
107+
// Call the hooks
108+
await hooks.onStart({ data: "test" }, {} as any);
109+
await hooks.onSuccess({ data: "test" }, { success: true });
110+
111+
// Verify the hooks were called
112+
console.log(verifyHookCalled(task, "onStartCalled")); // true
113+
console.log(verifyHookCalled(task, "onSuccessCalled")); // true
114+
```
115+
116+
## API Reference
117+
118+
### Task Mocking
119+
120+
- `mockTask(options)`: Creates a mock task that can be used for testing.
121+
- `isMockTask(task)`: Checks if a task is a mock task.
122+
- `createMockTaskForUnitTest(options)`: Creates a mock task with a custom run function.
123+
- `mockTaskDependencies(taskToTest, dependencies)`: Mocks dependencies for a specific task.
124+
125+
### Task Testing
126+
127+
- `executeMockTask(task, payload, options)`: Executes a task with mocked dependencies.
128+
- `testTaskFunction(task, payload, params, options)`: Tests a task's run function directly.
129+
- `setupMockTaskEnvironment(setupFn)`: Sets up a test environment with mocked tasks.
130+
131+
### Trigger Verification
132+
133+
- `verifyTaskTriggered(task, expectedPayload)`: Verifies that a task was triggered with the expected payload.
134+
- `getTaskTriggerCount(task)`: Gets the number of times a task was triggered.
135+
- `clearAllMockTriggers()`: Clears all mock triggers.
136+
137+
### Hook Testing
138+
139+
- `createMockHooks(task)`: Creates mock hooks for a task.
140+
- `verifyHookCalled(task, hookType)`: Verifies that a specific hook was called for a task.

packages/test/package.json

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{
2+
"name": "@trigger.dev/test",
3+
"version": "4.0.0-v4-beta.16",
4+
"description": "Testing utilities for trigger.dev tasks",
5+
"license": "MIT",
6+
"publishConfig": {
7+
"access": "public"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/triggerdotdev/trigger.dev",
12+
"directory": "packages/test"
13+
},
14+
"type": "module",
15+
"files": [
16+
"dist"
17+
],
18+
"tshy": {
19+
"selfLink": false,
20+
"main": true,
21+
"module": true,
22+
"project": "./tsconfig.build.json",
23+
"exports": {
24+
"./package.json": "./package.json",
25+
".": "./src/index.ts"
26+
},
27+
"sourceDialects": [
28+
"@triggerdotdev/source"
29+
]
30+
},
31+
"typesVersions": {
32+
"*": {
33+
".": [
34+
"dist/commonjs/index.d.ts"
35+
]
36+
}
37+
},
38+
"scripts": {
39+
"clean": "rimraf dist .tshy .tshy-build .turbo",
40+
"build": "tshy && pnpm run update-version",
41+
"dev": "tshy --watch",
42+
"typecheck": "tsc --noEmit",
43+
"update-version": "tsx ../../scripts/updateVersion.ts",
44+
"check-exports": "attw --pack .",
45+
"test": "vitest"
46+
},
47+
"dependencies": {
48+
"@trigger.dev/core": "workspace:4.0.0-v4-beta.16"
49+
},
50+
"devDependencies": {
51+
"@arethetypeswrong/cli": "^0.15.4",
52+
"@trigger.dev/sdk": "workspace:4.0.0-v4-beta.16",
53+
"rimraf": "^3.0.2",
54+
"tshy": "^3.0.2",
55+
"tsx": "4.17.0",
56+
"vitest": "^1.6.0",
57+
"zod": "3.23.8"
58+
},
59+
"peerDependencies": {
60+
"@trigger.dev/sdk": "^4.0.0-v4-beta.16",
61+
"zod": "^3.0.0"
62+
},
63+
"engines": {
64+
"node": ">=18.20.0"
65+
},
66+
"exports": {
67+
"./package.json": "./package.json",
68+
".": {
69+
"import": {
70+
"@triggerdotdev/source": "./src/index.ts",
71+
"types": "./dist/esm/index.d.ts",
72+
"default": "./dist/esm/index.js"
73+
},
74+
"require": {
75+
"types": "./dist/commonjs/index.d.ts",
76+
"default": "./dist/commonjs/index.js"
77+
}
78+
}
79+
},
80+
"main": "./dist/commonjs/index.js",
81+
"types": "./dist/commonjs/index.d.ts",
82+
"module": "./dist/esm/index.js"
83+
}

packages/test/src/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { mockTask, isMockTask, createMockTaskForUnitTest, mockTaskDependencies } from "./mockTask.js";
2+
import { executeMockTask, testTaskFunction } from "./taskTesting.js";
3+
import { setupMockTaskEnvironment } from "./mockTaskRegistry.js";
4+
import { verifyTaskTriggered, getTaskTriggerCount, clearAllMockTriggers } from "./mockTrigger.js";
5+
import { createMockHooks, verifyHookCalled } from "./mockHooks.js";
6+
7+
export * from "./types.js";
8+
export * from "./mockTask.js";
9+
export * from "./mockTrigger.js";
10+
export * from "./mockHooks.js";
11+
export * from "./taskTesting.js";
12+
export * from "./mockTaskRegistry.js";
13+
14+
export const triggerTest = {
15+
mockTask,
16+
isMockTask,
17+
createMockTaskForUnitTest,
18+
mockTaskDependencies,
19+
20+
executeMockTask,
21+
testTaskFunction,
22+
23+
setupMockTaskEnvironment,
24+
25+
verifyTaskTriggered,
26+
getTaskTriggerCount,
27+
clearAllMockTriggers,
28+
29+
createMockHooks,
30+
verifyHookCalled,
31+
};

packages/test/src/mockHooks.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Task, TaskIdentifier, TaskOutput, TaskPayload } from "@trigger.dev/sdk/v3";
2+
import { MockHooksCallInfo, TaskRunContext } from "./types.js";
3+
4+
/**
5+
* A registry of all hook calls
6+
*/
7+
class MockHooksRegistry {
8+
private hookCalls: Record<string, MockHooksCallInfo> = {};
9+
10+
/**
11+
* Record a hook call
12+
*/
13+
recordHookCall<TPayload = any, TOutput = any, TError = any>(
14+
taskId: string,
15+
hookType: keyof MockHooksCallInfo,
16+
payload?: TPayload,
17+
output?: TOutput,
18+
error?: TError
19+
) {
20+
if (!this.hookCalls[taskId]) {
21+
this.hookCalls[taskId] = {
22+
onStartCalled: false,
23+
onSuccessCalled: false,
24+
onFailureCalled: false,
25+
onCompleteCalled: false,
26+
};
27+
}
28+
29+
const hookCallInfo = this.hookCalls[taskId];
30+
31+
hookCallInfo[hookType] = true;
32+
33+
if (payload !== undefined) {
34+
hookCallInfo.payload = payload;
35+
}
36+
37+
if (output !== undefined) {
38+
hookCallInfo.output = output;
39+
}
40+
41+
if (error !== undefined) {
42+
hookCallInfo.error = error;
43+
}
44+
}
45+
46+
/**
47+
* Get hook calls for a specific task
48+
*/
49+
getHookCallsForTask(taskId: string): MockHooksCallInfo | undefined {
50+
return this.hookCalls[taskId];
51+
}
52+
53+
/**
54+
* Clear all hook calls
55+
*/
56+
clearHookCalls() {
57+
this.hookCalls = {};
58+
}
59+
}
60+
61+
export const mockHooksRegistry = new MockHooksRegistry();
62+
63+
/**
64+
* Create mock hooks for a task
65+
*/
66+
export function createMockHooks<
67+
TIdentifier extends string,
68+
TInput = void,
69+
TOutput = unknown
70+
>(
71+
task: Task<TIdentifier, TInput, TOutput>
72+
) {
73+
return {
74+
onStart: async (payload: TInput, ctx: TaskRunContext) => {
75+
mockHooksRegistry.recordHookCall(task.id, "onStartCalled", payload);
76+
},
77+
onSuccess: async (payload: TInput, output: TOutput) => {
78+
mockHooksRegistry.recordHookCall(task.id, "onSuccessCalled", payload, output);
79+
},
80+
onFailure: async (payload: TInput, error: Error) => {
81+
mockHooksRegistry.recordHookCall(task.id, "onFailureCalled", payload, undefined, error);
82+
},
83+
onComplete: async (payload: TInput, result: { ok: boolean; data?: TOutput; error?: Error }) => {
84+
mockHooksRegistry.recordHookCall(
85+
task.id,
86+
"onCompleteCalled",
87+
payload,
88+
result.ok ? result.data : undefined,
89+
result.ok ? undefined : result.error
90+
);
91+
},
92+
};
93+
}
94+
95+
/**
96+
* Verify that a specific hook was called for a task
97+
*/
98+
export function verifyHookCalled<
99+
TIdentifier extends string,
100+
TInput = void,
101+
TOutput = unknown
102+
>(
103+
task: Task<TIdentifier, TInput, TOutput>,
104+
hookType: keyof Omit<MockHooksCallInfo, "payload" | "output" | "error">
105+
): boolean {
106+
const hookCalls = mockHooksRegistry.getHookCallsForTask(task.id);
107+
108+
if (!hookCalls) {
109+
return false;
110+
}
111+
112+
return hookCalls[hookType];
113+
}

0 commit comments

Comments
 (0)