Files

5.4 KiB

Java SDK Testing

Overview

You test Temporal Java Workflows using TestWorkflowEnvironment (manual setup) or TestWorkflowExtension (JUnit 5). Activity mocking uses Mockito. The SDK provides WorkflowReplayer for replay-based compatibility testing.

Workflow Test Environment

import io.temporal.testing.TestWorkflowExtension;
import io.temporal.testing.TestWorkflowEnvironment;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import io.temporal.worker.Worker;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class MyWorkflowTest {

    @RegisterExtension
    public static final TestWorkflowExtension testWorkflowExtension =
        TestWorkflowExtension.newBuilder()
            .setWorkflowTypes(MyWorkflowImpl.class)
            .setDoNotStart(true)
            .build();

    @Test
    void testWorkflow(TestWorkflowEnvironment env, Worker worker, WorkflowClient client) {
        worker.registerActivitiesImplementations(new MyActivitiesImpl());
        env.start();

        MyWorkflow workflow = client.newWorkflowStub(
            MyWorkflow.class,
            WorkflowOptions.newBuilder()
                .setTaskQueue(worker.getTaskQueue())
                .build());

        String result = workflow.run("input");
        assertEquals("expected", result);
    }
}

For manual lifecycle control (e.g., JUnit 4 or custom setups), use TestWorkflowEnvironment directly with @BeforeEach/@AfterEach.

Mocking Activities

import static org.mockito.Mockito.*;

@Test
void testWithMockedActivities(
        TestWorkflowEnvironment env,
        Worker worker,
        WorkflowClient client) {
    // withoutAnnotations() prevents Mockito from copying Temporal annotations
    MyActivities activities = mock(MyActivities.class, withSettings().withoutAnnotations());
    when(activities.composeGreeting("Hello", "World")).thenReturn("mocked result");

    worker.registerActivitiesImplementations(activities);
    env.start();

    MyWorkflow workflow = client.newWorkflowStub(
        MyWorkflow.class,
        WorkflowOptions.newBuilder()
            .setTaskQueue(worker.getTaskQueue())
            .build());

    String result = workflow.run("input");
    assertEquals("mocked result", result);
    verify(activities).composeGreeting("Hello", "World");
}

Testing Signals and Queries

@Test
void testSignalsAndQueries(
        TestWorkflowEnvironment env,
        Worker worker,
        WorkflowClient client) {
    worker.registerActivitiesImplementations(new MyActivitiesImpl());
    env.start();

    MyWorkflow workflow = client.newWorkflowStub(
        MyWorkflow.class,
        WorkflowOptions.newBuilder()
            .setTaskQueue(worker.getTaskQueue())
            .build());

    // Start workflow asynchronously
    WorkflowClient.start(workflow::run, "input");

    // Send signal
    workflow.mySignal("data");

    // Query state
    String status = workflow.getStatus();
    assertEquals("expected", status);

    // Wait for completion
    String result = WorkflowStub.fromTyped(workflow).getResult(String.class);
}

Testing Failure Cases

import io.temporal.client.WorkflowException;

@Test
void testActivityFailure(
        TestWorkflowEnvironment env,
        Worker worker,
        WorkflowClient client) {
    MyActivities activities = mock(MyActivities.class, withSettings().withoutAnnotations());
    when(activities.unreliableAction(anyString()))
        .thenThrow(new RuntimeException("Simulated failure"));

    worker.registerActivitiesImplementations(activities);
    env.start();

    MyWorkflow workflow = client.newWorkflowStub(
        MyWorkflow.class,
        WorkflowOptions.newBuilder()
            .setTaskQueue(worker.getTaskQueue())
            .build());

    assertThrows(WorkflowException.class, () -> workflow.run("input"));
}

Workflow Replay Testing

import io.temporal.testing.WorkflowReplayer;

@Test
void testReplayFromHistory() throws Exception {
    WorkflowReplayer.replayWorkflowExecutionFromResource(
        "my-workflow-history.json",
        MyWorkflowImpl.class);
}

Replay from a WorkflowHistory object:

import io.temporal.common.WorkflowExecutionHistory;

@Test
void testReplayFromJsonString() throws Exception {
    String historyJson = new String(Files.readAllBytes(Paths.get("history.json")));
    WorkflowReplayer.replayWorkflowExecution(
        WorkflowExecutionHistory.fromJson(historyJson),
        MyWorkflowImpl.class);
}

Activity Testing

Activity implementations are plain Java classes. Test them directly:

@Test
void testActivity() {
    MyActivitiesImpl activities = new MyActivitiesImpl();
    String result = activities.composeGreeting("Hello", "World");
    assertEquals("Hello World", result);
}

For activities that use Activity.getExecutionContext() or heartbeating, use TestActivityEnvironment to provide the activity context.

Best Practices

  1. Use TestWorkflowExtension with JUnit 5 for concise test setup
  2. Always use withSettings().withoutAnnotations() when mocking activity interfaces with Mockito
  3. Mock external dependencies in activities, not in workflows
  4. Test replay compatibility when changing workflow code (see references/java/determinism.md)
  5. Test signal/query handlers explicitly
  6. Use unique task queues per test to avoid conflicts (handled automatically by TestWorkflowExtension)