Activity Retry Simulator tool purpose
The Activity Retry Simulator is a tool to visualize total Activity Execution times and experiment with different Activity timeouts and Retry Policies. The simulator is based on a common Activity use-case of calling a third party HTTP API and returning the results.
Activity Retries settings configuration
Use the Activity Retries settings to configure how long the API request takes to succeed or fail. There is an option to generate scenarios. The Task Time in Queue setting simulates the time the Activity Task might be waiting in the Task Queue.
Activity Timeouts and Retry Policy impact
Activity Timeouts and Retry Policy settings can be used to see how they impact the success or failure of an Activity Execution.
Retry policy time calculator available
A calculator tool is available to help compute Activity Task Execution times based on retry policy parameters like initial interval, max interval, max retries, and backoff coefficient.
Testing provisioning workflow with mocked activities
The Test_ProvisionTRUWorkflow test uses testsuite.WorkflowTestSuite to create a TestWorkflowEnvironment that simulates the Temporal Service in memory. It registers DeprovisionTRUWorkflow with the test environment since the parent starts a Child Workflow asynchronously. It mocks the AddTRUs Activity using env.OnActivity(a.AddTRUs, mock.Anything, mock.Anything).Return(nil) to prevent real network calls. After calling env.ExecuteWorkflow with a ProvisionTRUInput, it verifies the workflow completed without error using env.IsWorkflowCompleted() and env.GetWorkflowError(), then confirms the mocked Activity executed using env.AssertExpectations(t).
Testing deprovisioning workflow with simulated time
The Test_DeprovisionTRUWorkflow test uses testsuite.WorkflowTestSuite to create a TestWorkflowEnvironment. When a Timer or workflow.Sleep blocks execution, the Temporal test environment automatically skips time forward, allowing the 5-minute (or any duration) sleep to complete instantly during testing. The test mocks the RemoveTRUs Activity using env.OnActivity(a.RemoveTRUs, mock.Anything, mock.Anything).Return(nil) to prevent live API calls. After execution, it verifies the workflow completed and that the deprovisioning Activity executed as expected.
.NET Replay Testing for versioning validation
To determine whether a .NET Workflow needs a patch or has been patched successfully, Replay Testing should be incorporated into the testing suite.
Recommended testing approach for Activity Retries
Test Activity Retries using the Fixed Wall-Time Retries pattern by setting both ScheduleToCloseTimeout and StartToCloseTimeout with a retry policy, then verifying that the Activity stops retrying when the budget expires and an ActivityError is delivered to the Workflow.
.NET test types: end-to-end, integration, and unit
Temporal .NET testing includes three types: end-to-end tests run a Temporal Server and Worker with all Workflows and Activities, starting and interacting with Workflows from a Client; integration tests run anything between end-to-end and unit, such as Activities with mocked Context, Workers with mock Activities and a Client to start Workflows, or Workflows with mocked SDK imports; unit tests run a piece of Workflow or Activity code with mocked dependencies.
.NET majority testing recommendation: integration tests
It is generally recommended to write the majority of tests as integration tests in .NET Temporal development.
Use test server for .NET end-to-end and integration tests with time skipping
Because the test server supports skipping time, use the test server for both end-to-end and integration tests with Workers in .NET.
.NET compatible testing frameworks
The .NET SDK is compatible with any testing framework and does not have a specific recommendation. Most .NET SDK samples use xUnit.
.NET WorkflowEnvironment.StartLocalAsync for non-time-skipping tests
A non-time-skipping Temporalio.Testing.WorkflowEnvironment can be started via StartLocalAsync, which supports all standard Temporal features. It is the real Temporal dev server packaged in the Temporal CLI, lazily downloaded on first use, and run as a sub-process in the background. Assuming tests properly use separate Task Queues, the same server can and should be reused across tests.
.NET WorkflowEnvironment.StartTimeSkippingAsync for time-skipping tests
A time-skipping Temporalio.Testing.WorkflowEnvironment can be started via StartTimeSkippingAsync, which is a reimplementation of the Temporal server with special time skipping capabilities. Unlike StartLocalAsync, this class is not thread safe nor safe for use with independent tests. It can be technically reused, but only for one test at a time because time skipping is locked/unlocked at the environment level. Developers are encouraged to run it per test needed.
.NET automatic time skipping in GetResultAsync
By calling ExecuteWorkflowAsync on the client with a time-skipping environment, GetResultAsync automatically skips time as much as it can, basically until the end of the Workflow or until an Activity is run.
.NET disable automatic time skipping with env.WithAutoTimeSkippingDisabled
To disable automatic time-skipping while waiting for a workflow result in .NET, run code as a lambda passed to env.WithAutoTimeSkippingDisabled or env.WithAutoTimeSkippingDisabledAsync.
.NET manual time skipping with WorkflowEnvironment.DelayAsync
Until a Workflow is waited on in a time-skipping environment, all time skipping is done manually via WorkflowEnvironment.DelayAsync.
.NET Activity testing with ActivityEnvironment
Unit testing an Activity in .NET is done via the Temporalio.Testing.ActivityEnvironment class. Simply instantiate the class, and any code inside RunAsync will be invoked inside the activity context.
.NET ActivityEnvironment available members
ActivityEnvironment provides the following members: Info (Activity info, defaulted to a basic set of values), Logger (Activity logger, defaulted to a null logger), Cancel(CancelReason) (helper to set the reason and cancel the source), CancelReason (cancel reason), CancellationTokenSource (token source for issuing cancellation), Heartbeater (callback invoked each heartbeat), WorkerShutdownTokenSource (token source for issuing Worker shutdown), and PayloadConverter (defaulted to default payload converter).
.NET mocking Activities in Workflow tests
When testing Workflows, Activities can be mocked by writing different/empty/fake/asserting Activity implementations and passing those to the Worker to have different activities called during the test. Activities are just methods with the [Activity] attribute.
.NET Workflow replay testing with WorkflowReplayer
Given a Workflow's history, it can be replayed locally to check for non-determinism errors using the WorkflowReplayer class. If there is a non-determinism, this will throw an exception.
.NET WorkflowReplayer usage example with JSON history
Example: var replayer = new WorkflowReplayer(new WorkflowReplayerOptions().AddWorkflow<MyWorkflow>()); await replayer.ReplayWorkflowAsync(WorkflowHistory.FromJson("my-workflow-id", historyJson));
.NET check past Workflow histories for determinism
Event history can be fetched individually from a Workflow handle or in a list. The following pattern checks that all Workflow histories for a certain Workflow type are safe with the current Workflow code: var replayer = new WorkflowReplayer(new WorkflowReplayerOptions().AddWorkflow<MyWorkflow>()); var listIter = client.ListWorkflowHistoriesAsync("WorkflowType = 'SayHello'"); await foreach (var result in replayer.ReplayWorkflowsAsync(listIter)) { if (result.ReplayFailure != null) { ExceptionDispatchInfo.Throw(result.ReplayFailure); } }
.NET SayHelloWorkflow test example
Example Workflow test with standard server: [Fact] public async Task SayHelloWorkflow_SimpleRun_Succeeds() { await using var env = await WorkflowEnvironment.StartLocalAsync(); using var worker = new TemporalWorker(env.Client, new TemporalWorkerOptions($"task-queue-{Guid.NewGuid()}").AddWorkflow<SayHelloWorkflow>()); await worker.ExecuteAsync(async () => { var result = await env.Client.ExecuteWorkflowAsync((SayHelloWorkflow wf) => wf.RunAsync("Temporal"), new(id: $"wf-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!)); Assert.Equal("Hello, Temporal!", result); }); }
.NET WaitADayWorkflow time-skipping test example
Example time-skipping test: [Fact] public async Task WaitADayWorkflow_SimpleRun_Succeeds() { await using var env = await WorkflowEnvironment.StartTimeSkippingAsync(); using var worker = new TemporalWorker(env.Client, new TemporalWorkerOptions($"task-queue-{Guid.NewGuid()}").AddWorkflow<WaitADayWorkflow>()); await worker.ExecuteAsync(async () => { var result = await env.Client.ExecuteWorkflowAsync((WaitADayWorkflow wf) => wf.RunAsync(), new(id: $"wf-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!)); Assert.Equal("all done", result); }); } This test runs almost instantly due to automatic time skipping.
.NET SignalWorkflow signal test example
Example testing a normal Signal: [Fact] public async Task SignalWorkflow_SendSignal_HasExpectedResult() { await using var env = await WorkflowEnvironment.StartTimeSkippingAsync(); using var worker = new TemporalWorker(env.Client, new TemporalWorkerOptions($"task-queue-{Guid.NewGuid()}").AddWorkflow<SignalWorkflow>()); await worker.ExecuteAsync(async () => { var handle = await env.Client.StartWorkflowAsync((SignalWorkflow wf) => wf.RunAsync(), new(id: $"wf-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!)); await handle.SignalAsync(wf => wf.SomeSignalAsync()); Assert.Equal("got signal", await handle.GetResultAsync()); }); }
.NET SignalWorkflow timeout test with manual time skipping
Example testing a Signal timeout: [Fact] public async Task SignalWorkflow_SignalTimeout_HasExpectedResult() { await using var env = await WorkflowEnvironment.StartTimeSkippingAsync(); using var worker = new TemporalWorker(env.Client, new TemporalWorkerOptions($"task-queue-{Guid.NewGuid()}").AddWorkflow<SignalWorkflow>()); await worker.ExecuteAsync(async () => { var handle = await env.Client.StartWorkflowAsync((SignalWorkflow wf) => wf.RunAsync(), new(id: $"wf-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!)); await env.DelayAsync(TimeSpan.FromSeconds(50)); Assert.Equal("got timeout", await handle.GetResultAsync()); }); }
.NET testing determinism changes with replay tests
Workflow determinism changes are tested by using the replay test feature with WorkflowReplayer. Given a Workflow's history, it can be replayed locally to check for non-determinism errors. If the code changes introduce non-determinism, the replay will throw an exception.
Observability checklist for testing
Before and during testing, ensure visibility into: Workflow Task and Activity failure rates, throughput limits and usage, Workflow and Activity end-to-end latencies, task latency and backlog depth, Event History size and event counts, Worker CPU, memory, and restart counts, gRPC error codes, and retry behavior.
Guiding principles for Temporal testing
Testing Temporal applications follows several core principles: failure is normal and applications must handle it; partial failure is often harder to deal with than total failure; recovery paths deserve as much testing as steady state; build observability before injecting failures; and testing is a continual process, never finished.
Kill all Workers then restart test procedure
Test killing all Workers processing a Task Queue then restarting them. On Kubernetes, scale pod count to zero with 'kubectl scale deployment <deployment-name> --replicas=0 -n <namespace>' then scale back up with '--replicas=3'. This validates at-least-once execution semantics, ensures Activities are idempotent and Workflows replay cleanly, and validates Task timeouts and retries. Watch for duplicate/improper Activity results, Workflow failures, and Workflow backlog growth and drain time.
Frequent Worker restart test procedure
Periodically restart a fixed or random percentage (20-30%) of the Worker fleet every few minutes. This mimics failure modes where Workers restart due to high CPU utilization and out-of-memory errors from compute-intensive logic in Activities. Ensures Temporal invalidates specific Sticky Task Queues and reschedules tasks to the associated non-Sticky Task Queue. Watch for replay latency, drop in Workflow and Activity completion, duplicate/improper Activity results, Workflow failures, and Workflow backlog growth and drain time.
Pre-load test setup expectations for success
Before load testing: (1) Have SDK metrics accessible, not just Cloud metrics; (2) Understand and predict what you should see from metrics including rate limiting (temporal_cloud_v1_total_action_throttled_count and temporal_cloud_v1_service_request_throttled_count), Workflow failures (temporal_cloud_v1_workflow_failed_count), Workflow execution time (workflow_endtoend_latency), high Cloud latency (temporal_cloud_v1_service_latency_p95), and Worker metrics (workflow_task_schedule_to_start_latency and activity_schedule_to_start_latency); (3) Determine throughput requirements ahead of time and work with account team to match Namespace capacity to avoid rate limiting; (4) Automate how the load test runs so you can start and stop it at will; (5) Define what success looks like with specific metrics and numbers in business terms.
Validate downstream load capacity test
Schedule a large number of Actions and Requests by starting many Workflows and increase the number until overloading downstream systems. This validates behavior of Temporal application and application dependencies under high load. Start Workflows at a rate to surpass throughput limits. Watch for downstream service error rates (HTTP 5xx, database errors), increased downstream service latency and saturation metrics, Activity failure rates with classification between retryable and non-retryable errors, Activity retry and backoff behavior against overloaded system, Workflow backlog growth and drain time, correctness and consistency of data ensuring Activity idempotency holds under duress, and Worker CPU/memory utilization.
Validate rate limiting behavior test
Schedule a large number of Actions and Requests by starting many Workflows and increase the number until rate limited to trigger temporal_cloud_v1_total_action_throttled_count or temporal_cloud_v1_service_request_throttled_count. This validates behavior of Cloud service under high load. In Temporal Cloud, the effect of rate limiting is increased latency, not lost work; Workers might take longer to complete Workflows. Procedure: optionally decrease test Namespace's rate limits to make it easier to hit limits, calculate current APS at current throughput, calculate Workflow throughput needed to surpass limits, then start Workflows at that rate. Watch for Worker behavior when rate limited, client behavior when rate limited, Temporal request and long_request failure rates, Workflow success rates, and Workflow latency rates.
Test region failover procedure
Trigger a High Availability failover event for a Namespace per the manual failovers documentation. This validates operational playbooks and automation are resilient, and ensures Worker and Namespace failover behavior works correctly. Real outages are messy and rarely isolated. Watch for Namespace availability, client and Worker connectivity to failover region, Workflow Task reassignments, and human-in-the-loop recovery steps.
Break downstream dependencies test
Intentionally break or degrade downstream dependencies used by Activities such as making databases read-only or unavailable, injecting high latency or error rates into external APIs, or throttling/pausing message queues and event streams. This validates that Activities are retryable, idempotent, and correctly timeout-bounded, and ensures Workflows make forward progress instead of livelocking on broken dependencies. Temporal guarantees Workflow durability, not dependency availability. Watch for Activity retry and backoff behavior, heartbeat effectiveness for long-running Activities, database connection exhaustion and retry storms, API timeouts vs Activity timeouts, and whether failures propagate as Signals, compensations, or Workflow-level errors.
Deploy Workflow change with versioning test
Deploy Workflow code that would introduce non-deterministic errors (NDEs) but use a versioning strategy to deploy successfully. Validate Workflow success and clear the backlog of tasks. This tests versioning strategy and patching discipline to build production confidence. Watch for Workflow Task failure reasons and effectiveness of versioning and patching patterns.
Deploy version causing NDEs then recover test
Deploy Workflow code that introduces non-deterministic errors (NDEs), then attempt rollback to a known-good version or apply versioning strategies to apply the new changes successfully, and clear or recover the backlog of tasks. This tests versioning strategy, patching discipline, and recovery tooling. Watch for Workflow Task failure reasons, backlog growth and drain time, and effectiveness of versioning and patching patterns.
Remove network connectivity to Namespace test
Temporarily block all network access between Workers and the Temporal service for a Namespace. This validates Worker retry behavior, Sticky Task Queue behavior, Worker recovery performance, backoff policies, and Workflow replay determinism under prolonged disconnection. Ensures no assumptions are made about always-on connectivity. Exercises Workflow Task timeouts vs retries, Activity retry semantics, and replay correctness after long gaps. On Kubernetes, apply a NetworkPolicy that denies egress from Worker pods to Temporal APIs. Alternatively use ToxiProxy, Chaos Mesh/Litmus NetworkChaos with full packet drop, or locally block ports with iptables or firewall rules. Watch for Workflow failures (replay, timeout), Workflow Task retries, Activity failures with classification (retryable vs non-retryable), and Worker CPU usage during reconnect storms.
Game day runbook before testing starts
Before starting tests: make sure people know you are testing and what scenarios you are trying; let teams that support the APIs you are calling know you are testing; reach out to Temporal Cloud Support and Account teams to coordinate; have dashboards available for SDK and Cloud metrics including task latency, backlog depth, Workflow failures, Activity failures; mute or route alerts appropriately; have known-good deployment artifact available; verify rollback and scale controls.
Game day runbook during testing
During testing: introduce one variable at a time, record start/stop times of each experiment, capture screenshots or logs of unexpected behavior, and track backlog growth and drain rate.
Game day runbook recovery validation
Validate recovery by checking that Workflows resume without manual intervention, no permanent Workflow Task failures occur (unless intentional), Activity retries behave as expected, and backlogs drain in predictable time.
Game day runbook after action review
After testing is complete: identify unclear alerts or missing metrics/alerts, update retry, timeout, or versioning policies, and document surprises and operational debt.
Production readiness criteria for Temporal applications
A Temporal system is ready for production if it survives connectivity issues, repeated failovers, greater than expected load, and mass Worker churn. Pre-production testing is about proving operability under stress and knowing what to do before going to production.
Testing Workflow determinism with Replayer
Use the Replayer to verify code changes against a saved Event History. Export history via 'temporal workflow show --workflow-id <id> --output json > history.json', then replay it against the current code. If the replay fails with a NondeterminismError, the code change is not backward-compatible. Run this test as part of continuous integration before deploying Workflow code changes.
Go use workflow.Sleep not time.Sleep in tests
In Workflow tests, use workflow.Sleep(ctx, duration) instead of time.Sleep(duration) because Temporal's test environment does not stub out time.Sleep().
Go manual time control with RegisterDelayedCallback
For fine-grained control over time progression in tests, use env.RegisterDelayedCallback(callback, delayDuration) to create a timer using the mock Workflow clock. When the timer fires, the callback is called. This is useful when you want precise control over when timers fire.
Go replay workflow execution from event history
Use worker.NewWorkflowReplayer() to replay a Workflow from its Event History. Register the Workflow with replayer.RegisterWorkflow(WorkflowFunction) and call replayer.ReplayWorkflowHistory(nil, history) to replay the Event History. This verifies the Workflow definition is compatible with the history from a deterministic point of view.
Go get workflow event history with GetWorkflowHistory
Retrieve the Event History of a Workflow using client.GetWorkflowHistory(ctx, id, runID, false, enums.HISTORY_EVENT_FILTER_TYPE_ALL_EVENT). Iterate through events with iter.HasNext() and iter.Next() to build a history.History object.
Go test suite struct combines testify and Temporal test framework
To set up a test suite for Workflows in Go, define a struct that embeds both suite.Suite from testify and testsuite.WorkflowTestSuite from Temporal. Add a property to hold an instance of testsuite.TestWorkflowEnvironment. This allows you to initialize the test environment in a setup method.
Go SetupTest method runs before each test
Implement a SetupTest method to create a new test environment before each test using s.env = s.NewTestWorkflowEnvironment(). This ensures each test runs in its own isolated sandbox.
Go AfterTest method validates mock expectations
Implement an AfterTest function where you assert that all mocks were called by invoking s.env.AssertExpectations(s.T()). Set test timeout using SetTestTimeout in the Workflow or Activity environment.
Go test types: end-to-end, integration, and unit
Three types of automated tests exist in Temporal: End-to-end tests run a Temporal Server and Worker with all Workflows and Activities, starting and interacting with Workflows from a Client. Integration tests fall between end-to-end and unit testing, such as running Activities with mocked Context, running Workers with mock Activities, or running Workflows with mocked SDK imports. Unit tests run a piece of Workflow or Activity code and mock any code it calls. Integration tests are generally recommended as the majority of tests.
Go test framework recommendation for end-to-end and integration tests
Use the test server for both end-to-end and integration tests with Workers because it supports skipping time.
Go NewTestActivityEnvironment for Activity testing
Use NewTestActivityEnvironment() from WorkflowTestSuite to mock Activity context when testing Activities in isolation. This allows you to test the Activity without creating a Worker.
Go Activity testing with ExecuteActivity
To test an Activity, call env.RegisterActivity(ActivityFunction) to register the Activity with the test environment, then call env.ExecuteActivity(ActivityFunction, args...) to execute it. Retrieve the result using result.Get(&variable).
Go listen to Activity heartbeats in tests
Set up a heartbeat listener using env.SetOnActivityHeartbeatListener(func(activityInfo *activity.Info, details converter.EncodedValues) { ... }). This callback is invoked whenever the Activity sends a heartbeat, allowing you to verify heartbeat details in tests.
Go cancel Activity in test using context.WithCancel
To test Activity cancellation, create a context with context.WithCancel(context.Background()), pass it to env.SetWorkerOptions(worker.Options{BackgroundActivityContext: ctx}), then call cancel() in a goroutine to cancel the Activity. Expect the Activity to return a cancellation error.
Go ExecuteWorkflow basic test structure
Execute a Workflow in tests using s.env.ExecuteWorkflow(WorkflowFunction, args...). Check if the Workflow completed with s.env.IsWorkflowCompleted(), check for errors with s.env.GetWorkflowError(), and retrieve results with s.env.GetWorkflowResult(&value).