Skip to main content

Unit and Regression Tests

Use Conductor SDKs and APIs to conduct unit and regression tests on your workflows.

Unit tests

In Conductor, the smallest unit you can test is a workflow. A unit test enables you to test the workflow logic, ensuring the correctness of the workflow definition for the following areas:

  • Terminal state—Given a specific input, the workflow reaches the terminal state in a COMPLETED or FAILED state.
  • Control flow—Given a specific input, the workflow executes a specific set of tasks. This is useful for testing branching, switching, and dynamic forks.
  • Task I/O—Task inputs are wired correctly from one task to another. This is useful if a task receives its input from the output of another task.

Test environment

The actual workflow is executed on a real Conductor server, ensuring you are testing behavior that will match the server's actual execution.

Tests can be run against a remote server (useful for integration tests) or a local containerized instance. It is recommended that you use test containers to manage your test environments cleanly.

Mock tasks

When unit-testing workflows, only Worker tasks can be mocked to isolate external dependencies on workers. All other system tasks and operators cannot be mocked and will be executed.

Methods for unit testing workflows

Conductor workflows can be unit-tested using the Test Workflow API. This approach is similar to unit-testing using mock objects in Java or similar languages.

Examples

Here are some examples for unit-testing Conductor workflows.

Pull workflows locally

To test a local workflow definition:

public class WorkflowTests extends BaseTest {
@Test
public void testWorkflow() {
WorkflowTestRequest testRequest = new WorkflowTestRequest();
testRequest.setName("parent-flow");
testRequest.setWorkflowDef(Util.getWorkflowDef("parent-flow"));
testRequest.setTaskRefToMockOutput(Map.of(
"lookup-person-ref", List.of(new TaskMock(COMPLETED, Map.of("name", "June", "id", 543321))),
"do-sensitive-work-ref", List.of(new TaskMock(COMPLETED, Map.of("_masked", Map.of("q", "q"), "_secrets", Map.of("z", "z")))),
"do-intensive-task-ref", List.of(new TaskMock(COMPLETED, Map.of("result", 3.14159))
)));
Workflow workflow = workflowClient.testWorkflow(testRequest);
assertEquals(3.14159, workflow.getOutput().get("result"));
}
}

The parent-flow workflow is defined as code here:

public class Util {
public static WorkflowDef getWorkflowDef(String workflowName) {
WorkflowTask lookupPersonTask = new WorkflowTask();
lookupPersonTask.setName("lookup-person");
lookupPersonTask.setTaskReferenceName("lookup-person-ref");
lookupPersonTask.setInputParameters(Map.of("query", "john"));

WorkflowTask doSensitiveWorkTask = new WorkflowTask();
doSensitiveWorkTask.setName("do-sensitive-work");
doSensitiveWorkTask.setTaskReferenceName("do-sensitive-work-ref");
doSensitiveWorkTask.setInputParameters(Map.of(
"_masked", Map.of("a", "b"),
"_secrets", Map.of("a", "b")
));

WorkflowTask doIntensiveTask = new WorkflowTask();
doIntensiveTask.setName("do-intensive-task");
doIntensiveTask.setTaskReferenceName("do-intensive-task-ref");
doIntensiveTask.setInputParameters(Map.of("_masked", "${do-sensitive-work-ref.output._masked}"));

List<WorkflowTask> tasks = List.of(lookupPersonTask, doSensitiveWorkTask, doIntensiveTask);
WorkflowDef workflowDef = new WorkflowDef();
workflowDef.setName(workflowName);
workflowDef.setVersion(1);
workflowDef.setTasks(tasks);
workflowDef.setDescription("Example workflow for orkes-worker-java demo");

return workflowDef;
}
}

Refer to the complete code here.

Pull workflows from server

To pull workflows from the server instead, you can simply omit testRequest.setWorkflowDef();.

public class WorkflowTests extends BaseTest {
@Test
public void testWorkflow() {
WorkflowTestRequest testRequest = new WorkflowTestRequest();
testRequest.setName("parent-flow");
testRequest.setTaskRefToMockOutput(Map.of(
"lookup-person-ref", List.of(new TaskMock(COMPLETED, Map.of("name", "June", "id", 543321))),
"do-sensitive-work-ref", List.of(new TaskMock(COMPLETED, Map.of("_masked", Map.of("q", "q"), "_secrets", Map.of("z", "z")))),
"do-intensive-task-ref", List.of(new TaskMock(COMPLETED, Map.of("result", 3.14159))
)));
Workflow workflow = workflowClient.testWorkflow(testRequest);
assertEquals(3.14159, workflow.getOutput().get("result"));
}
}
Modify workflows before testing

Here is an example of modifying a workflow definition retrieved from a server and using testRequest.setWorkflowDef() to test the modified workflow definition. You can use metadataClient.getWorkflowDef to retrieve the workflow definition.

In this example, the workflow definition is modified so that all Sub Workflow tasks are replaced with a mock workflow dummy that has been added beforehand to the server.

@Test
public void testWorkflow() {
String workflowName = "parent-flow";
int workflowVersion = 1;

// Substitute all sub-flows to use `dummy` flow, e.g., containing an inline task
WorkflowDef workflowDef = metadataClient.getWorkflowDef(workflowName, workflowVersion);
workflowDef.getTasks().stream()
.filter(t -> t.getType().equals("SUB_WORKFLOW"))
.forEach(t -> t.getSubWorkflowParam().setName("dummy"));

WorkflowTestRequest testRequest = new WorkflowTestRequest();
testRequest.setName(workflowName);
testRequest.setVersion(workflowVersion);
testRequest.setWorkflowDef(workflowDef); // Use this instead of deployed definition
Workflow workflow = workflowClient.testWorkflow(testRequest);

assertEquals(3, workflow.getOutput().get("result"));
}
Test workflows with Worker tasks

Here is an example of using testRequest.setTaskRefToMockOutput() to mock Worker tasks.

/**
* Unit test a workflow with inputs read from a file.
*/
public class LoanWorkflowTest extends AbstractWorkflowTests {

/**
* Uses mock inputs to verify the workflow execution and input/outputs of the tasks
*/
@Test
public void verifyWorkflowExecutionWithMockInputs() throws IOException {
WorkflowDef def = getWorkflowDef("/workflows/calculate_loan_workflow.json");
assertNotNull(def);
Map<String, List<WorkflowTestRequest.TaskMock>> testInputs = getTestInputs("/test_data/loan_workflow_input.json");
assertNotNull(testInputs);

WorkflowTestRequest testRequest = new WorkflowTestRequest();
testRequest.setWorkflowDef(def);


LoanWorkflowInput workflowInput = new LoanWorkflowInput();
workflowInput.setUserEmail("user@example.com");
workflowInput.setLoanAmount(new BigDecimal(11_000));
testRequest.setInput(objectMapper.convertValue(workflowInput, Map.class));

testRequest.setTaskRefToMockOutput(testInputs);
testRequest.setName(def.getName());
testRequest.setVersion(def.getVersion());

Workflow execution = workflowClient.testWorkflow(testRequest);
assertNotNull(execution);

Refer to the complete code here.

Test workflows with Sub Workflow tasks

Here is an example of testing workflows that contain Sub Workflow tasks.

/**
* Demonstrates how to test workflows that contain sub-workflows
*/
public class SubWorkflowTest extends AbstractWorkflowTests {


@Test
public void verifySubWorkflowExecutions() throws IOException {
WorkflowDef def = getWorkflowDef("/workflows/kitchensink.json");
assertNotNull(def);

WorkflowDef subWorkflowDef = getWorkflowDef("/workflows/PopulationMinMax.json");
metadataClient.registerWorkflowDef(subWorkflowDef);


WorkflowTestRequest testRequest = getWorkflowTestRequest(def);

//The following are the dynamic tasks which are not present in the workflow definition but are created by dynamic fork
testRequest.getTaskRefToMockOutput().put("_x_test_worker_0_0", List.of(new WorkflowTestRequest.TaskMock()));
testRequest.getTaskRefToMockOutput().put("_x_test_worker_0_1", List.of(new WorkflowTestRequest.TaskMock()));
testRequest.getTaskRefToMockOutput().put("_x_test_worker_0_2", List.of(new WorkflowTestRequest.TaskMock()));
testRequest.getTaskRefToMockOutput().put("simple_task_1__1", List.of(new WorkflowTestRequest.TaskMock()));
testRequest.getTaskRefToMockOutput().put("simple_task_5", List.of(new WorkflowTestRequest.TaskMock()));


Workflow execution = workflowClient.testWorkflow(testRequest);
assertNotNull(execution);

Refer to the complete code here.

Test workflows with sub-workflows that contain Worker tasks

Here is an example of testing workflows that contain sub-workflows with Worker tasks.

@Test
public void testWorkflow() {
WorkflowTestRequest testRequest2 = new WorkflowTestRequest();
testRequest2.setName("subflow-flow");
testRequest2.setWorkflowDef(Util.getWorkflowDef("parent-flow"));
testRequest2.setTaskRefToMockOutput(Map.of(
"lookup-person-ref", List.of(new TaskMock(COMPLETED, Map.of("name", "June", "id", 543321))),
"do-sensitive-work-ref", List.of(new TaskMock(COMPLETED, Map.of("_masked", Map.of("q", "q"), "_secrets", Map.of("z", "z")))),
"do-intensive-task-ref", List.of(new TaskMock(COMPLETED, Map.of("result", 3.14159))
)));

WorkflowTestRequest testRequest = new WorkflowTestRequest();
testRequest.setName("parent-flow");
testRequest.setWorkflowDef(Util.getWorkflowDef("parent-flow"));
testRequest.setSubWorkflowTestRequest(Map.of(
"sub-workflow-ref-1", testRequest2
));
}

Regression tests

Regression tests are useful for ensuring that changes to a workflow definition do not cause unwanted changes in its behavior. This is particularly important when modifying active workflows in production.

Workflows can be regression tested with golden inputs and outputs.

Examples

Here is an example that uses a previous version of a workflow execution as the golden input/output to verify the new version of the workflow execution.

/**
* This test demonstrates how to use execution data from previously executed workflows as golden input and output
* and use them to regression test the workflow definition.
* <p>
* Regression tests are useful, ensuring any changes to the workflow definition do not change the behavior.
*/
public class RegressionTest extends AbstractWorkflowTests {

@Test
//Uses a previously executed successful run to verify the workflow execution, and its output.
public void verifyWorkflowOutput() throws IOException, ExecutionException, InterruptedException, TimeoutException {
//Workflow Definition
WorkflowDef def = getWorkflowDef("/workflows/workflow1.json");

//Golden output to verify against
Workflow workflow = getWorkflow("/test_data/workflow1_run.json");

WorkflowTestRequest testRequest = new WorkflowTestRequest();
testRequest.setInput(new HashMap<>());
testRequest.setName(def.getName());
testRequest.setVersion(def.getVersion());
testRequest.setWorkflowDef(def);

Map<String, List<WorkflowTestRequest.TaskMock>> taskRefToMockOutput = new HashMap<>();
for (Task task : workflow.getTasks()) {
List<WorkflowTestRequest.TaskMock> taskRuns = new ArrayList<>();
WorkflowTestRequest.TaskMock mock = new WorkflowTestRequest.TaskMock();
mock.setStatus(TaskResult.Status.valueOf(task.getStatus().name()));
mock.setOutput(task.getOutputData());
taskRuns.add(mock);
taskRefToMockOutput.put(def.getTasks().get(0).getTaskReferenceName(), taskRuns);
}

testRequest.setTaskRefToMockOutput(taskRefToMockOutput);
Workflow execution = workflowClient.testWorkflow(testRequest);
assertNotNull(execution);
assertEquals(workflow.getTasks().size(), execution.getTasks().size());
}


}

Refer to the full RegressionTest.java file for the complete code.