Files
Donald Pinckney b5719bc143 PR Tracking Initial Release (#4)
* Add initial skill for testing, which is simply Steve's skill (#1)

* Add initial skill for testing, which is simply Steve's skill

* Rename skill to 'temporal-dev' and update version

Updated skill name and version for Temporal Python.

* Use claude to merge Steve's, Max's, and Mason's skills.  (#2)

* Use claude to merge Steve's, Max's, and Mason's skills. Did a review pass using claude's skill devlopment skills

* Add missing things from Steve

* trigger tweaks

* Add in common gotchas from Johann

* add simple feedback mechanism (#3)

* Change skill name to kebab-case, for compatibility with Amp and Cline (#7)

* Clean up references/core/ai-integration.md

* Clean up references/core/common-gotchas.md

* Clean up references/core/common-gotchas.md

* Clean up references/core/determinism.md

* Clean up references/core/determinism.md

* Update error-reference.md

* Update interactive-workflows.md

* Clean up patterns.md

* Cut shell scripts

* Edit troubleshooting.md

* remove interceptors for now

* remove dynamic workflows

* clarify on heartbeating of async activity completions, and prompt it a bit in relation to signals

* Improve references/python/advanced-features.md

* Use explicit namespace in connect

* remove duplicated content from determinism.md, clean up

* Improve references/python/data-handling.md

* Prefer start_to_close_timeout

* don't explicitely provide defaults for retry policies

* error-handling.md cleanup

* move idempotency patterns to patterns.md

* remove multi-param activities

* small edits

* Unify sandbox stuff into one file

* local activities aren't experimental

* Clean up references/python/sync-vs-async.md

* Cleanup observability.md, remove duplicated search attributes

* Cut otel for now

* cut a lot of duplicate stuff from python gotchas, address comments

* de-duplicate content

* Lots of improvements to testing

* cleanup to top level of skill (like CLI install instructions), and to top-level of python

* Improve patterns.md

* clean up ai-patterns.md

* Update readme with installation instructions

* remove ts directory

* De-couple core from python and TypeScript as much as possible

* Remove TypeScript hints

* add prompting for feedback at startup - wait for ethan on slack channel

* shorten url

* Update slack channel

* Automated pass over on python cleanup & deduplication

* Remove multi-patching from Python, since its obvious, dont waste tokens on it. (#34)

* Add TypeScript (#31)

Adds initial support for TypeScript to the skill

---------

Co-authored-by: James Watkins-Harvey <mjameswh@users.noreply.github.com>
Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com>

* Fix typos and reference links (#36)

* Fix typos and reference links

* 2 more typo fixes

* quick edit to readme (#37)

* Fix saga compensations to run under cancellation protection (#43)

When a workflow is cancelled mid-saga, compensations must run in a
cancellation-protected scope, otherwise they are immediately cancelled
before they can execute.

- Python: wrap compensation loop in asyncio.shield() so it runs even
  when the workflow receives a CancelledError
- TypeScript: wrap compensation loop in CancellationScope.nonCancellable()
  so it runs even when the root scope is cancelled (per official docs:
  "Cleanup logic must be in a nonCancellable scope")
- TypeScript: also fix compensation registration order — register BEFORE
  calling the activity (was already correct in Python)

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* Update readme for public preview (#45)

* a few more readme tweaks (#46)

* Add MIT License to the project (#47)

* Add Go (supersedes other PR) (#38)

* progress on go

* Go translation workflow completed.

* missed a few spots

* Manual edits

* Address feedback

* Add gotcha about anonymous local activities

* Sample code for payload converter

* clarify sdk protection mechanisms

* Setup CODEOWNERS to AI SDK team (#48)

* Align version number in SKILL.md and plugin.json. (#49)

---------

Co-authored-by: James Watkins-Harvey <mjameswh@users.noreply.github.com>
Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 17:36:15 -04:00

166 lines
4.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Python SDK Testing
## Overview
You test Temporal Python Workflows using the Temporal testing package plus a normal Python test framework like pytest. The Temporal Python SDK provides `WorkflowEnvironment` for testing workflows in a local environment and `ActivityEnvironment` for isolated activity testing.
## Workflow Test Environment
The core pattern is:
1. Start a test WorkflowEnvironment (`WorkflowEnvironment.start_local()`).
2. Start a Worker in that environment with your Workflow and Activities registered.
3. Use the environment’s client to execute the Workflow, using a fresh UUID for the task queue name and workflow ID.
4. Assert on the result or status.
`WorkflowEnvironment.start_local` configures a ready-to-go local environment for running and testing workflows:
```python
import uuid
import pytest
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import Worker
from activities import my_activity
from workflows import MyWorkflow
@pytest.mark.asyncio
async def test_workflow():
task_queue_name = str(uuid.uuid4())
async with await WorkflowEnvironment.start_local() as env:
async with Worker(
env.client,
task_queue=task_queue_name,
workflows=[MyWorkflow],
activities=[my_activity],
):
result = await env.client.execute_workflow(
MyWorkflow.run,
"input",
id=str(uuid.uuid4()),
task_queue=task_queue_name,
)
```
Conveniently, the local `env` can be shared among tests, e.g. via a pytest fixture.
If your workflows / tests involve long durations (such as using Temporal timers / sleeps), then you can use the time-skipping environment, via `WorkflowEnvironment.start_time_skipping()`.
Only use time-skipping if you must. It can *not* be shared among tests.
## Mocking Activities
```python
import uuid
import pytest
from temporalio import activity
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import Worker
from workflows import MyWorkflow
@activity.defn(name="compose_greeting")
async def compose_greeting_mocked(input: str) -> str:
return "mocked result"
@pytest.mark.asyncio
async def test_with_mock():
task_queue_name = str(uuid.uuid4())
async with await WorkflowEnvironment.start_local() as env:
async with Worker(
env.client,
task_queue=task_queue_name,
workflows=[MyWorkflow],
activities=[compose_greeting_mocked],
):
result = await env.client.execute_workflow(...)
```
## Testing Signals and Queries
```python
@pytest.mark.asyncio
async def test_signals():
async with await WorkflowEnvironment.start_local() as env:
async with Worker(...):
handle = await env.client.start_workflow(...) # same arguments as to execute_workflow
# Send signal
await handle.signal(MyWorkflow.my_signal, "data")
# Query state
status = await handle.query(MyWorkflow.get_status)
assert status == "expected"
# Wait for completion
result = await handle.result()
```
## Testing Failure Cases
Below shows an example of how to test failure cases:
```python
# Test failure scenarios
@pytest.mark.asyncio
async def test_activity_failure_handling():
async with await WorkflowEnvironment.start_local() as env:
# An example activity that always fails
@activity.defn
async def failing_activity() -> str:
raise ApplicationError("Simulated failure", non_retryable=True)
async with Worker(...):
with pytest.raises(WorkflowFailureError):
await env.client.execute_workflow(...)
```
## Workflow Replay Testing
```python
import json
import pytest
import uuid
from temporalio.client import WorkflowHistory
from temporalio.worker import Replayer
from workflows import MyWorkflow
@pytest.mark.asyncio
async def test_replay():
with open("example-history.json", "r") as f:
history_json = json.load(f)
replayer = Replayer(workflows=[MyWorkflow])
# From JSON file
await replayer.replay_workflow(
WorkflowHistory.from_json(workflow_id=str(uuid.uuid4()), history_json)
)
```
## Activity Testing
```python
import pytest
from temporalio.testing import ActivityEnvironment
@pytest.mark.asyncio
async def test_activity():
env = ActivityEnvironment()
result = await env.run(my_activity, "arg1", "arg2")
assert result == "expected"
```
## Best Practices
1. Use the `WorkflowEnvironment.start_local` environment for most testing
2. Use time-skipping environment for workflows with durable timers / durable sleeps.
3. Mock external dependencies in activities
4. Test replay compatibility, especially when changing workflow code
5. Test signal/query handlers explicitly
6. Use unique workflow IDs and task queues per test to avoid conflicts. Easiest is a `uuid.uuid4()`