@@ -87,21 +87,20 @@ Create a test file in the same directory:
8787
8888``` typescript
8989import { handler } from " ./{example-name}" ;
90- import { createTests } from " ../../shared/ test-helper" ; // For nested : "../../../shared /test-helper"
90+ import { createTests } from " ../../../utils/ test-helper" ; // For standalone : "../../utils /test-helper"
9191
9292createTests ({
93- name: " my-example test" ,
94- functionName: " {example-name}" ,
9593 handler ,
96- tests : (runner ) => {
97- it (" should return expected result" , async () => {
94+ tests : (runner , { assertEventSignatures } ) => {
95+ it (" should execute successfully with expected result and operations " , async () => {
9896 const execution = await runner .run ();
99- expect (execution .getResult ()).toEqual (" example result" );
100- });
10197
102- it ( " should execute correct number of operations " , async () => {
103- const execution = await runner . run ( );
98+ // Multiple assertions on the same execution
99+ expect ( execution . getResult ()). toEqual ( " example result " );
104100 expect (execution .getOperations ()).toHaveLength (2 ); // adjust based on your example
101+
102+ // REQUIRED: Must call assertEventSignatures for every test
103+ assertEventSignatures (execution );
105104 });
106105 },
107106});
@@ -172,38 +171,147 @@ The `createTests` helper provides a unified interface:
172171
173172``` typescript
174173createTests ({
175- name: string ; // Test suite name
176- functionName : string ; // Must match handler filename (without .ts)
177- handler : Function ; // The handler function to test
178- invocationType ?: string ; // Optional: 'RequestResponse' | 'Event'
179- tests : (runner , isCloud ) => void ; // Test definitions
174+ handler: DurableLambdaHandler ; // The handler function to test
175+ tests : TestCallback < ResultType > ; // Test definitions
176+ invocationType ?: InvocationType ; // Optional: 'RequestResponse' | 'Event'
177+ localRunnerConfig ?: LocalDurableTestRunnerSetupParameters ; // Optional local test config
180178});
181179```
182180
183181Inside ` tests ` , you have access to:
184182
185183- ` runner ` : Either ` LocalDurableTestRunner ` or ` CloudDurableTestRunner `
186- - ` isCloud ` : Boolean indicating if running against real Lambda
184+ - ` testHelper ` : Object containing:
185+ - ` assertEventSignatures ` : ** Required** function to validate execution history
186+ - ` isTimeSkipping ` : Boolean indicating if time is being skipped in tests
187+ - ` isCloud ` : Boolean indicating if running against real Lambda
188+ - ` functionNameMap ` : Helper for resolving function names in tests
189+
190+ ## Event Signature Validation with ` assertEventSignatures `
191+
192+ ** IMPORTANT** : Every test ** MUST** call ` assertEventSignatures(execution) ` at the end. This validates that the execution produces the expected sequence of durable execution events.
193+
194+ ### How it Works
195+
196+ 1 . ** First Run** : When you first create a test, run it with ` GENERATE_HISTORY=true ` to create the history file:
197+
198+ ``` bash
199+ GENERATE_HISTORY=true npm test
200+ ```
201+
202+ 2 . ** History File Creation** : This generates a ` .history.json ` file next to your test containing the expected event signatures.
203+
204+ 3 . ** Subsequent Runs** : Normal test runs compare the actual events against the stored history file.
205+
206+ ### Example Usage
207+
208+ ``` typescript
209+ createTests ({
210+ handler ,
211+ tests : (runner , { assertEventSignatures }) => {
212+ it (" should complete workflow successfully" , async () => {
213+ const execution = await runner .run ();
214+
215+ // Your test assertions
216+ expect (execution .getResult ()).toEqual (" completed" );
217+ expect (execution .getOperations ()).toHaveLength (3 );
218+
219+ // REQUIRED: Validate event signatures
220+ assertEventSignatures (execution );
221+ });
222+
223+ it (" should handle callback operations" , async () => {
224+ const callbackOp = runner .getOperation (" my-callback" );
225+ const executionPromise = runner .run ();
226+
227+ await callbackOp .waitForData ();
228+ await callbackOp .sendCallbackSuccess (" result" );
229+
230+ const execution = await executionPromise ;
231+ expect (execution .getResult ()).toEqual (" result" );
232+
233+ // REQUIRED: Validate event signatures
234+ assertEventSignatures (execution );
235+ });
236+ },
237+ });
238+ ```
239+
240+ ### Multiple History Files
241+
242+ For tests with multiple scenarios, you can create separate history files:
243+
244+ ``` typescript
245+ it (" should handle success case" , async () => {
246+ const execution = await runner .run ({ scenario: " success" });
247+ expect (execution .getResult ()).toBe (" success" );
248+
249+ // Creates/uses example-name-success.history.json
250+ assertEventSignatures (execution , " success" );
251+ });
252+
253+ it (" should handle failure case" , async () => {
254+ const execution = await runner .run ({ scenario: " failure" });
255+ expect (execution .getError ()).toBeDefined ();
256+
257+ // Creates/uses example-name-failure.history.json
258+ assertEventSignatures (execution , " failure" );
259+ });
260+ ```
187261
188262### Common Test Patterns
189263
190264``` typescript
191- tests : (runner , isCloud ) => {
192- it (" should return expected result" , async () => {
265+ tests : (runner , { assertEventSignatures , isCloud , isTimeSkipping }) => {
266+ // Combine tests with identical setup (same runner.run() call)
267+ it (" should execute successfully with expected result and operations" , async () => {
193268 const execution = await runner .run ();
269+
270+ // Multiple assertions on the same execution
194271 expect (execution .getResult ()).toEqual (expectedValue );
195- } );
272+ expect ( execution . getOperations ()). toHaveLength ( 3 );
196273
197- it (" should execute operations in order" , async () => {
198- const execution = await runner .run ();
274+ // Check operations in order
199275 const ops = execution .getOperations ();
200- expect (ops [0 ].name ).toBe (" step-1" );
201- expect (ops [1 ].name ).toBe (" step-2" );
276+ expect (ops [0 ].getName ()).toBe (" step-1" );
277+ expect (ops [1 ].getName ()).toBe (" step-2" );
278+
279+ // REQUIRED
280+ assertEventSignatures (execution );
281+ });
282+
283+ // Separate test only when setup is different (different parameters, callbacks, etc.)
284+ it (" should handle callback operations" , async () => {
285+ const callbackOp = runner .getOperation (" my-callback" );
286+ const executionPromise = runner .run ();
287+
288+ // Wait for callback to start
289+ await callbackOp .waitForData ();
290+
291+ // Send callback result
292+ await callbackOp .sendCallbackSuccess (" callback-result" );
293+
294+ const execution = await executionPromise ;
295+ expect (execution .getResult ()).toContain (" callback-result" );
296+
297+ // REQUIRED
298+ assertEventSignatures (execution );
202299 });
203300
204- it (" should execute correct number of operations" , async () => {
301+ // Environment-specific tests with different setups
302+ it (" should behave differently in cloud vs local" , async () => {
205303 const execution = await runner .run ();
206- expect (execution .getOperations ()).toHaveLength (3 );
304+
305+ if (isCloud ) {
306+ // Cloud-specific assertions
307+ expect (execution .getInvocations ().length ).toBeGreaterThan (1 );
308+ } else {
309+ // Local-specific assertions
310+ expect (isTimeSkipping ).toBe (true );
311+ }
312+
313+ // REQUIRED
314+ assertEventSignatures (execution );
207315 });
208316};
209317```
@@ -213,6 +321,9 @@ tests: (runner, isCloud) => {
213321- [ ] Created example file in appropriate directory structure
214322- [ ] Created test file in same directory
215323- [ ] Used correct import paths for test-helper and types
324+ - [ ] Added ` assertEventSignatures ` parameter to test callback
325+ - [ ] Called ` assertEventSignatures(execution) ` in every test
326+ - [ ] Generated history files with ` GENERATE_HISTORY=true npm test `
216327- [ ] Local tests pass (` npm test ` )
217328- [ ] Integration tests pass in CI/CD
218329
@@ -231,6 +342,31 @@ sam local execution history $DURABLE_EXECUTION_ARN
231342
232343## Troubleshooting
233344
234- ** Test not found in integration run:**
345+ ### assertEventSignatures Issues
346+
347+ ** Error: "assertEventSignature was not called for test [ name] "**
348+
349+ - You forgot to call ` assertEventSignatures(execution) ` in one or more of your tests
350+ - Make sure every ` it() ` test calls this function
351+
352+ ** Error: "History file [ ...] .history.json does not exist"**
353+
354+ - Run the test with ` GENERATE_HISTORY=true npm test ` to create the history file
355+ - Make sure the file is committed to your repository
356+
357+ ** Error: Event signature mismatch**
358+
359+ - The execution produced different events than expected
360+ - If this is intentional (you changed the function), regenerate the history with ` GENERATE_HISTORY=true npm test `
361+ - If not intentional, check your function logic for unintended changes
362+
363+ ** TypeError: testResult.getHistoryEvents is not a function**
364+
365+ - You're passing the wrong variable to ` assertEventSignatures `
366+ - Pass the ` execution ` result from ` runner.run() ` , not ` execution.getResult() `
367+
368+ ### Test Setup Issues
369+
370+ ** Tests timing out:**
235371
236- - Verify ` functionName ` in test matches the example name
372+ - For local tests with time skipping disabled: make sure step retries are not longer than the timeout
0 commit comments