How to test starting/signaling a workflow in Java?

I’d like to test code that is attempting to signalWithStart a workflow (note: I’m not trying to test the workflow itself). The code under test has a dependency on WorkflowClient. Oversimplified example (Kotlin):

class ClientExample(
    val client: WorkflowClient,
) {
    fun sendStringAsSignal(workflowId: String, signalValue: String) {
        val workflow = client.newWorkflowStub(
            MyWorkflow::class.java,
            WorkflowOptions.newBuilder()
                .setTaskQueue("TaskQueue")
                .setWorkflowId(workflowId)
                .build()
        )
        WorkflowStub.fromTyped(workflow).signalWithStart(
            "signalName",
            arrayOf(signalValue),
            arrayOf()
        )
    }
}

How can I test that my WorkflowStub was created with the correct arguments and that the workflow signal method was invoked with the correct arguments?

I cannot easily mock the client since I would need to heavily mock multiple layers: newWorkflowStub, static WorkflowStub.fromTyped, etc.

I see that Go has a Client mock but I don’t see any similar offering in the Java SDK (maybe mocking is more idiomatic in Go?).

The simplest is to implement a workflow that validates these. Then run it using TestWorkflowEnvironment.

1 Like

Thank you. I’ll explore that approach. I suppose I could create a test-specific implementation of my real workflow interface as well.

Is there a way to name the task queue used by the TestEnvironment so that I can test which task queue I’m targeting?

You can set it through options, but I would just check against the value of Workflow.getInfo().getTaskQueue().

1 Like

Thanks again @maxim. I’m taking this approach now.

Some FYIs to anyone reading this in the future, and optionally as feedback to Temporal team:

  1. It looks like TestWorkflowExtension doesn’t currently support configuring the task queue, but you can configure task queue with testEnv.newWorker when using the manual approach.
  2. When invoking a workflow with signalWithStart, it doesn’t block for completion, nor does the returned WorkflowExecution offer an easy way to await the execution to finish. The best way that I could find was to construct a new workflow stub and to invoke the workflow in a blocking fashion.

I ultimately ended up with something like this (Kotlin):

val expectedTaskQueue = "ExpectedTaskQueue"
val testEnv = TestWorkflowEnvironment.newInstance()
val worker = testEnv.newWorker(expectedTaskQueue)
val client = testEnv.workflowClient
val myService = MyServiceImpl(client)

class TestWorkflowImpl : MyWorkflow {
    var signaled = false;

    override fun startWorkflow(input: MyWorkflowInput): MyWorkflowOutput {
        val success = Workflow.await(Duration.ofSeconds(5)) { signaled }
        if (!success) {
            throw TimeoutException("Timed out waiting for signal")
        }
        return MyWorkflowOutput()
    }

    override fun signal(input: SignalInput) {
        signaled = true
    }
}

@BeforeEach
fun setUp() {
    worker.registerWorkflowImplementationTypes(TestWorkflowImpl::class.java)
    testEnv.start()
}

@AfterEach
fun tearDown() {
    testEnv.close()
}

@Test
@Timeout(value = 10, unit = TimeUnit.SECONDS)
fun testSubmit() {
    // Given
    val input = UUID.randomUUID().toString()

    // When
    val workflowExecution = myService.submit(input)

    // Await
    val workflowStub = client.newUntypedWorkflowStub(workflowExecution.workflowId)
    val workflowOutput = workflowStub.getResult<MyWorkflowOutput>()

    // Then
    assertThat(workflowExecution.workflowId).isEqualTo(...)
    assertThat(workflowOutput).isEqualTo(MyWorkflowOutput())
}

Feedback welcome on any ways to simplify this, but it is cleaner than trying to mock WorkflowClient/WorkflowStub.

You also can use an untyped stub to wait:

WorkflowStub untyped = client.newUntypedWorkflowStub(workflowExecution, Optional.of("MyWorkflow"));
untyped.getResult(...);

Nice! Thank you. Updated my code snippet to use this approach.