Skip to content

Add Durable Execution Testing Library#2447

Open
GarrettBeatty wants to merge 5 commits into
devfrom
durabletesting3
Open

Add Durable Execution Testing Library#2447
GarrettBeatty wants to merge 5 commits into
devfrom
durabletesting3

Conversation

@GarrettBeatty

@GarrettBeatty GarrettBeatty commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Issue #, if available:

Description of changes:

Adds Amazon.Lambda.DurableExecution.Testing (preview) — test durable workflows without deploying to AWS. You code against IDurableTestRunner<TInput, TOutput> and the same test runs unchanged against the in-memory DurableTestRunner (fast, no AWS) or the CloudDurableTestRunner (a deployed function).

Basic workflow

Construct a runner with your handler, call RunAsync, and assert on the TestResult — including individual steps:

await using var runner = new DurableTestRunner<int, int>(
    handler: async (input, ctx) =>
    {
        var doubled = await ctx.StepAsync(async (_, _) => input * 2, name: "double");
        return await ctx.StepAsync(async (_, _) => doubled + 10, name: "add_ten");
    });

var result = await runner.RunAsync(5);

result.EnsureSucceeded();
Assert.Equal(20, result.Result);

var doubleStep = result.GetStep("double");
Assert.Equal(OperationKind.Step, doubleStep.Kind);
Assert.Equal(OperationStatus.Succeeded, doubleStep.Status);
Assert.Equal(10, doubleStep.GetResult<int>());

Time-skipping

By default waits and retry backoffs complete immediately, so a 30-day wait runs in milliseconds — and is still recorded as a step you can assert on:

await ctx.WaitAsync(TimeSpan.FromDays(30), name: "long_wait");
// ...
var wait = result.GetStep("long_wait");
Assert.Equal(OperationKind.Wait, wait.Kind);

Set TestRunnerOptions.SkipTime = false to assert on real durations.

Callbacks

For workflows that suspend on a callback, use the two-call pattern: StartAsync runs until suspension, WaitForCallbackAsync returns the pending callback id, you send a result, then WaitForResultAsync drives it to completion:

var arn        = await runner.StartAsync("request-1");
var callbackId = await runner.WaitForCallbackAsync(arn, name: "approval");

await runner.SendCallbackSuccessAsync(callbackId, "yes");
var result = await runner.WaitForResultAsync(arn);

result.EnsureSucceeded();
Assert.Equal("approved: yes", result.Result);

SendCallbackFailureAsync and SendCallbackHeartbeatAsync are also available.

Sibling functions

When a workflow calls ctx.InvokeAsync, register the target on the runner so it resolves in-process:

// A plain (non-durable) Lambda handler
runner.RegisterFunction<PaymentRequest, PaymentResult>(
    "process-payment",
    (req, _) => Task.FromResult(new PaymentResult { Status = $"approved-{req.Amount}" }));

// A durable sibling runs in its own nested runner with its own steps
runner.RegisterDurableFunction<PaymentRequest, PaymentResult>(
    "audit",
    async (req, childCtx) => await childCtx.StepAsync(/* ... */));

Testing against a deployed function

CloudDurableTestRunner implements the same IDurableTestRunner interface against a real deployed durable function, so portable tests run unchanged on either backend:

await using var runner = new CloudDurableTestRunner<Order, OrderResult>(
    functionArn: "arn:aws:lambda:us-east-1:123456789012:function:order-processor:live");

var result = await runner.RunAsync(new Order(/* ... */));
result.EnsureSucceeded();

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Amazon.Lambda.DurableExecution.Tests, PublicKey="0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4"</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think this change requires a new version to be released in order for the durableexecution.testing package to build since testing package uses a project refrerence right

Base automatically changed from durableinterface to dev June 26, 2026 15:57
Update durable-execution-testing-cloud-runner-history.json

Add InternalsVisibleTo for Amazon.Lambda.DurableExecution.Testing

Remove MIGRATION_PLAN.md
@GarrettBeatty GarrettBeatty changed the title add durable testing Add Durable Execution Testing Library Jun 26, 2026
<TargetFrameworks>$(DefaultPackageTargets)</TargetFrameworks>
<Description>Testing utilities for Amazon Lambda Durable Execution - test durable workflows locally without deploying to AWS.</Description>
<AssemblyTitle>Amazon.Lambda.DurableExecution.Testing</AssemblyTitle>
<Version>0.0.1-preview</Version>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had to manually set as -preview here as well. i will also do an override when doing the first release to make it be preview.

without setting it here, build fails because a non preview package would reference a preview package (durable execution)

@GarrettBeatty GarrettBeatty marked this pull request as ready for review June 26, 2026 18:42
@GarrettBeatty GarrettBeatty requested review from a team as code owners June 26, 2026 18:42
@GarrettBeatty GarrettBeatty requested review from normj and philasmar June 26, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant