1+ """
2+ Workflow Unit Testing Example
3+ ==============================
4+
5+ This module demonstrates how to write unit tests for Conductor workflows and workers.
6+
7+ Key Concepts:
8+ -------------
9+ 1. **Worker Testing**: Test worker functions independently as regular Python functions
10+ 2. **Workflow Testing**: Test complete workflows end-to-end with mocked task outputs
11+ 3. **Mock Outputs**: Simulate task execution results without running actual workers
12+ 4. **Retry Simulation**: Test retry logic by providing multiple outputs (failed then succeeded)
13+ 5. **Decision Testing**: Verify switch/decision logic with different input scenarios
14+
15+ Test Types:
16+ -----------
17+ - **Unit Test (test_greetings_worker)**: Tests a single worker function in isolation
18+ - **Integration Test (test_workflow_execution)**: Tests complete workflow with mocked dependencies
19+
20+ Running Tests:
21+ --------------
22+ python3 -m unittest discover --verbose --start-directory=./
23+ python3 -m unittest examples.test_workflows.WorkflowUnitTest
24+
25+ Use Cases:
26+ ----------
27+ - Validate workflow logic before deployment
28+ - Test error handling and retry behavior
29+ - Verify decision/switch conditions
30+ - CI/CD pipeline integration
31+ - Regression testing for workflow changes
32+ """
33+
134import unittest
235
336from conductor .client .configuration .configuration import Configuration
1144
1245class WorkflowUnitTest (unittest .TestCase ):
1346 """
14- This is an example of how to write a UNIT test for the workflow
15- to run:
16-
17- python3 -m unittest discover --verbose --start-directory=./
47+ Unit tests for Conductor workflows and workers.
1848
49+ This test suite demonstrates:
50+ - Testing individual worker functions
51+ - Testing complete workflow execution with mocked task outputs
52+ - Simulating task failures and retries
53+ - Validating workflow decision logic
1954 """
2055 @classmethod
2156 def setUpClass (cls ) -> None :
@@ -26,33 +61,75 @@ def setUpClass(cls) -> None:
2661
2762 def test_greetings_worker (self ):
2863 """
29- Tests for the workers
30- Conductor workers are regular python functions and can be unit or integrated tested just like any other function
64+ Unit test for a worker function.
65+
66+ Demonstrates:
67+ - Worker functions are regular Python functions that can be tested directly
68+ - No need to start worker processes or connect to Conductor server
69+ - Fast, isolated testing of business logic
70+ - Can use standard Python testing tools (unittest, pytest, etc.)
71+
72+ This approach is ideal for:
73+ - Testing worker logic in isolation
74+ - Running tests in CI/CD pipelines
75+ - Test-driven development (TDD)
76+ - Quick feedback during development
3177 """
3278 name = 'test'
3379 result = greet (name = name )
3480 self .assertEqual (f'Hello { name } ' , result )
3581
3682 def test_workflow_execution (self ):
3783 """
38- Test a complete workflow end to end with mock outputs for the task executions
84+ Integration test for a complete workflow with mocked task outputs.
85+
86+ Demonstrates:
87+ - Testing workflow logic without running actual workers
88+ - Mocking task outputs to simulate different scenarios
89+ - Testing retry behavior (task failure followed by success)
90+ - Testing decision/switch logic with different inputs
91+ - Validating workflow execution paths
92+
93+ Key Benefits:
94+ - Fast execution (no actual task execution)
95+ - Deterministic results (mocked outputs)
96+ - No external dependencies (no worker processes)
97+ - Test error scenarios safely
98+ - Validate workflow structure and logic
99+
100+ Workflow Structure:
101+ -------------------
102+ 1. HTTP task (always succeeds)
103+ 2. task1 (fails first, succeeds on retry with city='NYC')
104+ 3. Switch decision based on task1.output('city')
105+ 4. If city='NYC': execute task2
106+ 5. Otherwise: execute task3
107+
108+ Expected Flow:
109+ --------------
110+ HTTP → task1 (FAILED) → task1 (RETRY, COMPLETED) → switch → task2
39111 """
112+ # Create workflow with tasks
40113 wf = ConductorWorkflow (name = 'unit_testing_example' , version = 1 , executor = self .workflow_executor )
41114 task1 = SimpleTask (task_def_name = 'hello' , task_reference_name = 'hello_ref_1' )
42115 task2 = SimpleTask (task_def_name = 'hello' , task_reference_name = 'hello_ref_2' )
43116 task3 = SimpleTask (task_def_name = 'hello' , task_reference_name = 'hello_ref_3' )
44117
118+ # Switch decision: if city='NYC' → task2, else → task3
45119 decision = SwitchTask (task_ref_name = 'switch_ref' , case_expression = task1 .output ('city' ))
46120 decision .switch_case ('NYC' , task2 )
47121 decision .default_case (task3 )
48122
123+ # HTTP task to simulate external API call
49124 http = HttpTask (task_ref_name = 'http' , http_input = {'uri' : 'https://orkes-api-tester.orkesconductor.com/api' })
50125 wf >> http
51126 wf >> task1 >> decision
52127
128+ # Mock outputs for each task
53129 task_ref_to_mock_output = {}
54130
55- # task1 has two attempts, first one failed and second succeeded
131+ # task1 has two attempts: first fails, second succeeds
132+ # This tests retry behavior
56133 task_ref_to_mock_output [task1 .task_reference_name ] = [{
57134 'status' : 'FAILED' ,
58135 'output' : {
@@ -62,11 +139,12 @@ def test_workflow_execution(self):
62139 {
63140 'status' : 'COMPLETED' ,
64141 'output' : {
65- 'city' : 'NYC'
142+ 'city' : 'NYC' # This triggers the switch to execute task2
66143 }
67144 }
68145 ]
69146
147+ # task2 succeeds (executed because city='NYC')
70148 task_ref_to_mock_output [task2 .task_reference_name ] = [
71149 {
72150 'status' : 'COMPLETED' ,
@@ -76,6 +154,7 @@ def test_workflow_execution(self):
76154 }
77155 ]
78156
157+ # HTTP task succeeds
79158 task_ref_to_mock_output [http .task_reference_name ] = [
80159 {
81160 'status' : 'COMPLETED' ,
@@ -85,26 +164,32 @@ def test_workflow_execution(self):
85164 }
86165 ]
87166
167+ # Execute workflow test with mocked outputs
88168 test_request = WorkflowTestRequest (name = wf .name , version = wf .version ,
89169 task_ref_to_mock_output = task_ref_to_mock_output ,
90170 workflow_def = wf .to_workflow_def ())
91171 run = self .workflow_client .test_workflow (test_request = test_request )
92172
173+ # Verify workflow completed successfully
93174 print (f'completed the test run' )
94175 print (f'status: { run .status } ' )
95176 self .assertEqual (run .status , 'COMPLETED' )
96177
178+ # Verify HTTP task executed first
97179 print (f'first task (HTTP) status: { run .tasks [0 ].task_type } ' )
98180 self .assertEqual (run .tasks [0 ].task_type , 'HTTP' )
99181
182+ # Verify task1 failed on first attempt (retry test)
100183 print (f'{ run .tasks [1 ].reference_task_name } status: { run .tasks [1 ].status } (expected to be FAILED)' )
101184 self .assertEqual (run .tasks [1 ].status , 'FAILED' )
102185
186+ # Verify task1 succeeded on retry
103187 print (f'{ run .tasks [2 ].reference_task_name } status: { run .tasks [2 ].status } (expected to be COMPLETED' )
104188 self .assertEqual (run .tasks [2 ].status , 'COMPLETED' )
105189
190+ # Verify switch decision executed task2 (because city='NYC')
106191 print (f'{ run .tasks [4 ].reference_task_name } status: { run .tasks [4 ].status } (expected to be COMPLETED' )
107192 self .assertEqual (run .tasks [4 ].status , 'COMPLETED' )
108193
109- # assert that the task2 was executed
194+ # Verify the correct branch was taken (task2, not task3)
110195 self .assertEqual (run .tasks [4 ].reference_task_name , task2 .task_reference_name )
0 commit comments