Skip to content

Commit

Permalink
feat: JUnit extension injects Zeebe client
Browse files Browse the repository at this point in the history
If the test class has a field with the type of the Zeebe client then the JUnit extension injects a Zeebe client for the current test run.

The injection can be used to start an external job worker or a process application for the test run.
  • Loading branch information
saig0 committed Jan 11, 2023
1 parent 3714fa9 commit a82e9de
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.junit.jupiter.api.extension.ExtendWith

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SpecRunnerProvider::class)
@ExtendWith(SpecRunnerProvider::class, SpecRunnerInjectCallback::class)
annotation class BpmnSpecRunner(
val resourceDirectory: String = "",
val verificationTimeout: String = "PT10S",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.zeebe.bpmnspec.junit

import io.camunda.zeebe.client.ZeebeClient
import io.zeebe.bpmnspec.api.SpecTestRunnerContext
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.platform.commons.util.ExceptionUtils
import org.junit.platform.commons.util.ReflectionUtils
import java.lang.reflect.Field
import kotlin.reflect.KClass

class SpecRunnerInjectCallback : BeforeEachCallback {

override fun beforeEach(context: ExtensionContext?) {
context
?.requiredTestInstances
?.allInstances
?.forEach {
injectFields(context, it, it.javaClass)
}
}

private fun injectFields(context: ExtensionContext, testInstance: Any, testClass: Class<*>) {

val specTestRunnerContext = context
.getStore(SpecRunnerProvider.extensionContextNamespace)
.get(SpecRunnerProvider.extensionContextStoreKey, SpecTestRunnerContext::class.java)
?: throw RuntimeException("Expect spec test runner context but not found")

injectFields(
specTestRunnerContext,
testInstance,
testClass,
ZeebeClient::class
) { it.getZeebeClient() }
}

private fun injectFields(
specTestRunnerContext: SpecTestRunnerContext,
testInstance: Any,
testClass: Class<*>,
fieldType: KClass<*>,
fieldValue: (SpecTestRunnerContext) -> Any
) {
val fields = ReflectionUtils.findFields(
testClass,
{ field: Field -> ReflectionUtils.isNotStatic(field) && field.type == fieldType.java },
ReflectionUtils.HierarchyTraversalMode.TOP_DOWN
)

fields.forEach { field: Field ->
try {
ReflectionUtils.makeAccessible(field)[testInstance] =
fieldValue(specTestRunnerContext)
} catch (t: Throwable) {
ExceptionUtils.throwAsUncheckedException(t)
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package io.zeebe.bpmnspec.junit

import io.zeebe.bpmnspec.ClasspathResourceResolver
import io.zeebe.bpmnspec.SpecRunner
import io.zeebe.bpmnspec.api.SpecTestRunnerContext
import io.zeebe.bpmnspec.runner.TestRunnerEnvironment
import io.zeebe.bpmnspec.runner.eze.EzeEnvironment
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ExtensionContext.Namespace
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
import java.time.Duration
Expand Down Expand Up @@ -57,10 +61,32 @@ class SpecRunnerProvider : ParameterResolver {
?.orElse(defaultVerificationRetryInterval)
?: defaultVerificationRetryInterval

val environment: TestRunnerEnvironment = EzeEnvironment()

// the context is used for injecting the Zeebe client
storeTestRunnerContext(extensionContext, environment.getContext())

return SpecRunner(
resourceResolver = resourceResolver,
environment = environment,
verificationTimeout = verificationTimeout,
verificationRetryInterval = verificationRetryInterval
)
}
}

private fun storeTestRunnerContext(
extensionContext: ExtensionContext?,
specTestRunnerContext: SpecTestRunnerContext
) {
extensionContext
?.getStore(extensionContextNamespace)
?.put(extensionContextStoreKey, specTestRunnerContext)
}

companion object {
val extensionContextNamespace: Namespace = Namespace.create(SpecRunnerProvider::class.java)

const val extensionContextStoreKey = "spec-runner-environment"
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.zeebe.bpmnspec.junit

import io.camunda.zeebe.client.ZeebeClient
import io.zeebe.bpmnspec.SpecRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.params.ParameterizedTest

@BpmnSpecRunner(verificationTimeout = "PT1S")
class BpmnSpecExtensionInjectionTest(private val specRunner: SpecRunner) {

private lateinit var zeebeClient: ZeebeClient

@BeforeEach
fun `start external worker`() {
zeebeClient.newWorker()
.jobType("a")
.handler { client, job ->
val valueOfX = job.variablesAsMap["x"] as Int
val newValue = valueOfX + 1

client.newCompleteCommand(job.key)
.variables(mapOf("x" to newValue))
.send()
.join()
}
.open()
}

@ParameterizedTest
@BpmnSpecSource(specResources = ["spec-with-external-worker.yaml"])
fun `should inject Zeebe client and complete process`(spec: BpmnSpecTestCase) {

assertThat(zeebeClient)
.describedAs("Zeebe client should be injected")
.isNotNull()

val testResult =
specRunner.runSingleTestCase(resources = spec.resources, testcase = spec.testCase)

assertThat(testResult.success)
.describedAs(testResult.message)
.isTrue()
}

}
31 changes: 31 additions & 0 deletions junit-extension/src/test/resources/spec-with-external-worker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
resources:
- exclusive-gateway.bpmn

testCases:
- name: activate task B
description: the external job worker should complete task A with 'x' > 5
actions:
- action: create-instance
args:
bpmn_process_id: exclusive-gateway
variables: '{"x":5}'

verifications:
- verification: element-instance-state
args:
element_name: B
state: activated

- name: activate task C
description: the external job worker should complete task A with 'x' <= 5
actions:
- action: create-instance
args:
bpmn_process_id: exclusive-gateway
variables: '{"x":3}'

verifications:
- verification: element-instance-state
args:
element_name: C
state: activated

0 comments on commit a82e9de

Please sign in to comment.