sdk-java
sdk-java copied to clipboard
Have a built-in way to override activityOptions in tests
Is your feature request related to a problem? Please describe. Request: Make it possible for test setup to override ActivityOptions set for activities in workflow.
Problem: In production code I have high retry count on activities, but in testing I want to override that to have maxAttempts=1 so that workflow gets it’s failure right away. Or in case I didn’t mock some activity properly the test will fail right away with NullPointerException instead of hanging indefinitely.
Another thing is that, when stubbing activities in workflow I hardcode the queue name, because it might not be the same the workflow is on. And in tests I need to override it to make things run, without reproducing all the queue-worker combinations in prod.
Describe the solution you'd like
Sth like overrideAllActivityOptions
or overrideActivityOptions
in example below.
import io.temporal.activity.ActivityOptions
import io.temporal.client.WorkflowException
import io.temporal.client.WorkflowOptions
import io.temporal.common.RetryOptions
import io.temporal.failure.ActivityFailure
import io.temporal.failure.ApplicationFailure
import io.temporal.testing.TestWorkflowEnvironment
import io.temporal.worker.WorkflowImplementationOptions
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.anyString
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
class HelloWorldWorkflowImplTest {
private val taskQueue = "TASK_QUEUE"
private val testEnv = TestWorkflowEnvironment.newInstance();
private val testActivityOptions = ActivityOptions.newBuilder()
.setTaskQueue(taskQueue)
.setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(1).validateBuildWithDefaults())
.validateAndBuildWithDefaults()
private val opts = WorkflowImplementationOptions.newBuilder()
.setFailWorkflowExceptionTypes(
Throwable::class.java,
)
.setDefaultActivityOptions(
testActivityOptions
)
.overrideAllActivityOptions(
testActivityOptions
)
.overrideActivityOptions(
mapOf(HelloWorldActivities::class.java to testActivityOptions)
)
.build()!!
private val worker = testEnv.newWorker(taskQueue).also {
it.registerWorkflowImplementationTypes(
opts,
HelloWorldWorkflowImpl::class.java
)
}
@Test
fun `activity error should fail workflow`() {
val formatActivities = mock<HelloWorldActivities>()
var count = 1
whenever(formatActivities.composeGreeting(anyString())).then {
println("CALL $count")
count += 1
error("test error")
}
worker.registerActivitiesImplementations(formatActivities)
testEnv.start()
val workflow = testEnv.workflowClient.newWorkflowStub(
HelloWorldWorkflow::class.java,
WorkflowOptions.newBuilder().setTaskQueue(taskQueue).build()!!
)
try {
workflow.getGreeting("Mock")
error("unreachable")
} catch (e: WorkflowException) {
assertTrue(e.cause is ActivityFailure)
assertTrue(e.cause?.cause is ApplicationFailure)
assertEquals(
"test error",
(e.cause?.cause as ApplicationFailure).originalMessage
)
}
}
}
Describe alternatives you've considered Right now I am flagging to code directly to use testActivityOptions when executed from test.
@BeforeEach
fun `setup overrides`() {
GlobalWorkflowOptions.set(testActivityOptions)
}
@AfterEach
fun `reset overrides`() {
GlobalWorkflowOptions.clear()
}
and in code
class HelloWorldWorkflowImpl : HelloWorldWorkflow {
private val activityOptions = ActivityOptions.newBuilder()
.setTaskQueue(HELLO_WORLD_TASK_QUEUE)
.setStartToCloseTimeout(Duration.ofSeconds(60))
.validateAndBuildWithDefaults()!!
private val activity = Workflow.newActivityStub(
HelloWorldActivities::class.java,
GlobalWorkflowOptions.activityOptions(activityOptions)
)
override fun getGreeting(name: String): String {
return activity.composeGreeting(name)
}
}
Additional context https://community.temporal.io/t/throwing-exception-in-mocked-activity-hangs-the-test/10932
Related issues: #499 #626
In the meantime, can you consider having your mock activity throw a non-retryable exception?