## What changed?
Add a default 90s timeout to individual tests using the
`testcore.NewEnv` for setup. Tests should use the `context.Context`
returned from `testcore.NewEnv(...)` to fail early when a test exceeds
the timeout.
## Why?
Go only allows to set a timeout for an entire test run via `go test`,
but a single test case can make the entire run be stuck for the full
test timeout time. This change will make those tests exit early speeding
up the dev process.
## How did you test it?
- [ ] built
- [X] run locally and tested manually
- [X] covered by existing tests
- [ ] added new unit test(s)
- [ ] added new functional test(s)
## Potential risks
Test change only, could introduce flakes if tests did not have previous
context timeouts.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches shared test infrastructure and changes how contexts/timeouts
are created and propagated across functional tests, which could
introduce new test flakes or premature cancellations if timeouts are too
aggressive.
>
> **Overview**
> Functional tests created via `testcore.NewEnv` now get a *test-scoped*
timeout context (default **90s**, configurable via
`TEMPORAL_TEST_TIMEOUT` or per-test `testcore.WithTimeout`) that is
canceled on timeout and fails the test during cleanup.
>
> `testcore.NewContext` is updated to optionally derive from a parent
context, and key functional tests are migrated to pass
`s.Context()`/`env.Context()` into RPC calls and
`context.WithTimeout(...)` to respect cancellations. Linting/docs are
updated to discourage `context.Background()` in tests and document the
new timeout knob.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
98852a5e43. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
7.0 KiB
Testing
This document describes the project's testing setup, utilities and best practices.
Setup
Build tags
test_dep: This Go build tag enables the test hooks implementation. Only very few tests require it; they will fail if not enabled.TEMPORAL_DEBUG: Extends functional test timeouts to allow sufficient time for debugging sessions.disable_grpc_modules: Disables gRPC modules for faster compilation during unit tests.
Environment variables
CGO_ENABLED: Set to0to disable CGO, which can significantly speed up compilation time.TEMPORAL_TEST_LOG_FORMAT: Controls the output format for test logs. Available options:jsonorconsoleTEMPORAL_TEST_LOG_LEVEL: Sets the verbosity level for test logging. Available levels:debug,info,warn,error,fatalTEMPORAL_TEST_OTEL_OUTPUT: Enables OpenTelemetry (OTEL) trace output for failed tests to the provided file path.TEMPORAL_TEST_SHARED_CLUSTERS: Number of shared clusters in the pool. Each can be used by multiple tests simultaneously.TEMPORAL_TEST_DEDICATED_CLUSTERS: Number of dedicated clusters in the pool. Each can be used by one test only at a time.TEMPORAL_TEST_TIMEOUT: Sets the duration timeout per test (e.g.,90sfor 90 seconds). This can be overridden per-test usingtestcore.WithTimeout(). The timeout is multiplied bydebug.TimeoutMultiplierwhen debugging.TEMPORAL_TEST_DATA_ENCODING: Controls the encoding used for persistence DataBlobs. Available options:proto3(default) orjson.
Debugging via IDE
GoLand
For general instructions, see GoLand Debugging. To pass in the required build tags, add them to the "Go tool arguments" field in the Run/Debug configuration:
-tags disable_grpc_modules,test_dep
Best Practices
Parallelization
All tests (and subtests!) should use t.Parallel() to be run concurrently;
unless there is a reason not to.
make parallelize-tests can be used to automatically add t.Parallel().
Use //parallelize:ignore to opt your test out of it.
Functional tests in tests/ using testcore.NewEnv(t) will always use t.Parallel();
unless the MustRunSequential option is passed.
Test helpers
Test helpers can be found in the common/testing package.
testvars package
Instead of creating identifiers like task queue name, namespace or worker identity by hand,
use the testvars package.
Example:
func TestFoo(t *testing.T) {
tv := testvars.New(t)
req := &workflowservice.SignalWithStartWorkflowExecutionRequest{
RequestId: tv.Any().String(),
Namespace: tv.NamespaceName().String(),
WorkflowId: tv.WorkflowID(),
WorkflowType: tv.WorkflowType(),
TaskQueue: tv.TaskQueue(),
SignalName: tv.SignalName(),
}
}
Later you can assert on the generated values. testvars guarantees to provide the same value every time you call the same method.
assert.Equal(t, tv.WorkflowID(), startedWorkflow.WorkflowId)
If you need more than one value for the same entity in one test, you can use WithEntityNumber() method to
get a new instance of testvars with a different value.
func TestFoo(t *testing.T) {
tv := testvars.New(t)
tv1 := tv.WithUpdateIDNumber(1)
tv2 := tv.WithUpdateIDNumber(2)
req1 := &workflowservice.UpdateWorkflowExecutionRequest{
Namespace: tv1.NamespaceName().String(),
WorkflowExecution: tv1.WorkflowExecution(),
Request: &updatepb.Request{
Meta: &updatepb.Meta{UpdateId: tv1.UpdateID()},
Input: &updatepb.Input{
Name: tv1.HandlerName(),
Args: payloads.EncodeString("args-value-of-" + tv1.UpdateID()),
},
},
}
req2 := &workflowservice.UpdateWorkflowExecutionRequest{
Namespace: tv2.NamespaceName().String(),
WorkflowExecution: tv2.WorkflowExecution(),
Request: &updatepb.Request{
Meta: &updatepb.Meta{UpdateId: tv2.UpdateID()},
Input: &updatepb.Input{
Name: tv2.HandlerName(),
Args: payloads.EncodeString("args-value-of-" + tv2.UpdateID()),
},
},
}
}
If you don't care about specific value, you can use Any() method to generate a random value.
It indicates that value doesn't matter for this test and will never be asserted on (but required for API, for example).
taskpoller package
For end-to-end testing, consider using taskpoller.TaskPoller to handle workflow tasks. This is
useful when you need full control over the worker behavior in a way that the SDK cannot provide;
or if there's no SDK support for that API available yet.
You'll find a fully initialized task poller in any functional test suite, look for s.TaskPoller.
NOTE: The previous testcore.TaskPoller has been deprecated and should not be used in new code.
testhooks package
The testhooks package injects test-specific behavior into production code paths that are otherwise
difficult to test. This is a last resort - prefer mocking and dependency injection when possible.
Example:
The UpdateWithStart API has a race window between releasing a lock and starting a workflow where
another request could create the same workflow first. The UpdateWithStartInBetweenLockAndStart
hook lets tests inject a callback at this exact point, making it possible to reliably test
conflict handling.
NOTE: Tests using testhooks must be run with -tags=test_dep.
softassert package
softassert.That is a "soft" assertion that logs an error if the given condition is false.
It is useful to highlight invariant violations in production code. It is not a substitute for regular error handling, validation, or control flow.
In functional tests, a failed soft assertion will not stop the test execution immediately, but it will ultimately fail the test.
Test Cluster
Use testcore.NewEnv(t) to create a test environment with access to a Temporal cluster for end-to-end testing.
func TestMyFeatureSuite(t *testing.T) {
t.Run("scenario one", func(t *testing.T) {
s := testcore.NewEnv(t)
// ...
})}
Note that each test has its own namespace (s.Namespace()) for isolation.
Note: The legacy
FunctionalTestBase(using testify'ssuite) has been deprecated for new tests.
OpenTelemetry (OTEL)
To debug your test by analysing observability traces, set the following environment variables:
export OTEL_BSP_SCHEDULE_DELAY=100
export OTEL_EXPORTER_OTLP_TRACES_INSECURE=true
export OTEL_TRACES_EXPORTER=otlp
export TEMPORAL_OTEL_DEBUG=true
And have an OTEL collector running, such as Grafana Tempo (make start-dependencies).
See tracing.md for more details.
Code coverage
You'll find the code coverage reporting in Codecov: https://app.codecov.io/gh/temporalio/temporal.
Consider installing the Codecov Browser Extension to see code coverage directly in GitHub PRs.