temporalio / temporalio/sdk-java
Have a built-in way to override activityOptions in tests
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 433
- Forks
- 249
- Avg merge
- 5d 6h
- Merged PRs (30d)
- 26
Description
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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with WorkflowImplementationOptions and the TestWorkflowEnvironment worker registration shown in the example, then trace how activity options are selected for workflow activity stubs. Define and verify behavior for default, all-activity, and per-activity overrides, including retry limits and task queues, using the failure scenario described in the issue.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend-api-design, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100