Hello Community!
I have seen questions around How do I test Axon @EventHandler
and the persistent answer has been to mock the Component/Class with the event/message handler and directly pass the message instance to the mock as evidenced here Testing an @EventHandler
This has been the only viable solution until testcontainers came along. Now, we can write & run unitigration
tests against real systems and dependencies, and have greater confidence in the software we ship, succinctly. Anyways, aren’t mocks sweet lies that we tell ourselves - debatable.
As I will demonstrate, I am capable of spinning up an axon server test container instance and dispatch events via the EventGateway, with a slight hiccup…
// Container Declaration
class TestContainersInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
...
private val axon = AxonServerContainer.builder()
.dockerImageVersion(AXON_IMAGE_VERSION)
.enableDevMode()
.build()
...
// Start container, and inject dynamic properties
override fun initialize(applicationContext: ConfigurableApplicationContext) {
...
axon.start()
...
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
...
"axon.axonserver.servers=${axon.url}",
"axon.serializer.general=jackson",
"axon.serializer.events=jackson",
"axon.serializer.messages=jackson"
...
)
Now, when I emit an event, I expect that my system handles it successfully as declared by the test…
@SpringBootTest
@ContextConfiguration(initializers = [TestContainersInitializer::class])
class PointReEventProcessorTest : BehaviorSpec() {
...
Given("A Student") {
val testStudent = studentService.addStudent(student)
When("A Student Rewarded Event is Published") {
val event = StudentRewarded(
studentId = testStudent.studentId,
rewardType = KPS.PointRewardType.DAILY_LOGIN_BONUS,
amount = 2.toBigInteger(),
)
eventPublisher.publish(event)
Then("The Student Should Have Been Awarded with 2 Points") {
val rewardedStudent = studentService.getStudent(studentId = testStudent.studentId, response = Student::class.java)
rewardedStudent.asClue {
rewardedStudent.balance shouldBe BigInteger.TWO
}
}
}
}
...
}
The message handler is as defined as such…
@EventHandler
@DisallowReplay
fun handle(event: StudentRewarded) {
val student = service.getStudent(studentId = event.studentId, response = Student::class.java)
student.reward(rewardId = UUID.randomUUID(), rewardType = event.rewardType, amount = event.amount)
service.updateStudent(student)
}
and the event is defined as such …
data class StudentRewarded(
val studentId: UUID,
val rewardType: PointRewardType,
val amount: BigInteger
)
When the test is executed, a runtime jackson deserialization exception is thrown…
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `KPS.StudentRewarded` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"{"studentId":"997de0e0-72b8-4b5c-aa04-5a01766af03d","rewardType":"DAILY_LOGIN_BONUS","amount":2}"; line: 1, column: 2]
...
io.kotest.assertions.AssertionFailedError: ...
Expected : 2
Actual : 0
As you can see, through the axon server, the test is capable of publishing
and capturing
events. Now, after combing through the codebase, it’s difficult to pinpoint the source of the exception as I think this configuration should suffice…
@Configuration
class AxonConfig {
@Bean
@Primary
fun serializer(): Serializer {
return JacksonSerializer
.builder()
.lenientDeserialization()
.build()
}
}
Could there be something I’m missing out? Your help is warmly welcome.
Thanks and Kind Regards.