diff --git a/extension/jgiven/core/pom.xml b/extension/jgiven/core/pom.xml
index 233c84c..0cb3257 100644
--- a/extension/jgiven/core/pom.xml
+++ b/extension/jgiven/core/pom.xml
@@ -75,6 +75,12 @@
1
test
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
ch.qos.logback
logback-classic
diff --git a/extension/jgiven/core/src/main/kotlin/AxonJGiven.kt b/extension/jgiven/core/src/main/kotlin/AxonJGiven.kt
index 2577ec8..f38c8a4 100644
--- a/extension/jgiven/core/src/main/kotlin/AxonJGiven.kt
+++ b/extension/jgiven/core/src/main/kotlin/AxonJGiven.kt
@@ -9,6 +9,10 @@ import io.holixon.axon.testing.jgiven.saga.SagaFixtureGiven
import io.holixon.axon.testing.jgiven.saga.SagaFixtureThen
import io.holixon.axon.testing.jgiven.saga.SagaFixtureWhen
import io.holixon.axon.testing.jgiven.saga.SagaTestFixtureBuilder
+import org.axonframework.eventhandling.DomainEventMessage
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+import java.util.function.Predicate
/**
* Base class for scenario aggregate tests.
@@ -27,6 +31,26 @@ object AxonJGiven {
inline fun aggregateTestFixtureBuilder() = AggregateTestFixtureBuilder(T::class.java)
inline fun sagaTestFixtureBuilder() = SagaTestFixtureBuilder(T::class.java)
+
+ internal fun Class<*>.getDeclaredAccessibleField(fieldName: String): Field = getDeclaredField(fieldName)
+ .apply {
+ isAccessible = true
+ }
+
+ internal fun Class<*>.getDeclaredAccessibleMethod(methodName: String): Method = getDeclaredMethod(methodName)
+ .apply {
+ isAccessible = true
+ }
+
+ fun listContainsEventPayloadAndMetaData(payload: Any, metaData: Map): Predicate>> = Predicate { list ->
+ val msgMeta = list.find { it.payload == payload }?.metaData ?: emptyMap()
+
+ // and if so, does it contain all required metaData?
+ metaData.entries.map { (k, v) ->
+ msgMeta.containsKey(k) && v == msgMeta[k]
+ }.reduce { acc, b -> acc && b }
+ }
+
}
/**
diff --git a/extension/jgiven/core/src/main/kotlin/aggregate/AggregateFixtureThen.kt b/extension/jgiven/core/src/main/kotlin/aggregate/AggregateFixtureThen.kt
index 269e3a4..6a273df 100644
--- a/extension/jgiven/core/src/main/kotlin/aggregate/AggregateFixtureThen.kt
+++ b/extension/jgiven/core/src/main/kotlin/aggregate/AggregateFixtureThen.kt
@@ -3,13 +3,19 @@
package io.holixon.axon.testing.jgiven.aggregate
import com.tngtech.jgiven.Stage
-import com.tngtech.jgiven.annotation.*
+import com.tngtech.jgiven.annotation.As
+import com.tngtech.jgiven.annotation.Hidden
+import com.tngtech.jgiven.annotation.ProvidedScenarioState
+import com.tngtech.jgiven.annotation.Quoted
+import io.holixon.axon.testing.jgiven.AxonJGiven
import io.holixon.axon.testing.jgiven.AxonJGivenStage
import io.holixon.axon.testing.jgiven.step
import org.axonframework.commandhandling.CommandResultMessage
import org.axonframework.deadline.DeadlineMessage
import org.axonframework.eventhandling.EventMessage
+import org.axonframework.test.aggregate.Reporter
import org.axonframework.test.aggregate.ResultValidator
+import org.axonframework.test.matchers.Matchers
import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert
import java.time.Duration
@@ -25,7 +31,9 @@ import kotlin.reflect.KClass
class AggregateFixtureThen : Stage>() {
@ProvidedScenarioState
- private var context: AggregateTestFixtureContext = AggregateTestFixtureContext()
+ private var context: AggregateTestFixtureContext = AggregateTestFixtureContext()
+
+ private val reporter = Reporter()
/**
* Expect event.
@@ -34,6 +42,21 @@ class AggregateFixtureThen : Stage>() {
@As("expect event:")
fun expectEvent(event: Any) = this.expectEvents(event)
+ @As("expect event:\$ with metaData:\$")
+ fun expectEventWithMetaData(event: Any, vararg entries: Pair) = step {
+ val identifier = checkNotNull(context.aggregateIdentifier) { "no aggregateIdentifier present" }
+
+ val events = context.fixture?.eventStore?.readEvents(identifier)?.asSequence()?.toList() ?: emptyList()
+
+ val predicate = AxonJGiven.listContainsEventPayloadAndMetaData(event, mapOf(*entries))
+
+ val matcher = Matchers.predicate(predicate)
+
+ if (!matcher.matches(events)) {
+ reporter.reportWrongResult(events, "Event exists with payload=$event and metaData=${mapOf(*entries)}.")
+ }
+ }
+
/**
* Expect a series of events.
* @param events events to expect.
@@ -172,6 +195,7 @@ class AggregateFixtureThen : Stage>() {
@As("expect result message")
fun expectResultMessage(message: CommandResultMessage<*>) = execute {
expectResultMessage(message)
+
}
/**
diff --git a/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureBuilder.kt b/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureBuilder.kt
index 3f771a3..f3dfcc0 100644
--- a/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureBuilder.kt
+++ b/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureBuilder.kt
@@ -9,6 +9,9 @@ import org.axonframework.messaging.MessageHandlerInterceptor
import org.axonframework.messaging.annotation.HandlerDefinition
import org.axonframework.messaging.annotation.HandlerEnhancerDefinition
import org.axonframework.messaging.annotation.ParameterResolverFactory
+import org.axonframework.messaging.correlation.CorrelationDataProvider
+import org.axonframework.messaging.correlation.MessageOriginProvider
+import org.axonframework.messaging.interceptors.CorrelationDataInterceptor
import org.axonframework.modelling.command.CommandTargetResolver
import org.axonframework.modelling.command.Repository
import org.axonframework.modelling.command.RepositoryProvider
@@ -36,6 +39,7 @@ class AggregateTestFixtureBuilder(private val aggregateType: Class) {
private lateinit var repository: Repository
private lateinit var repositoryProvider: RepositoryProvider
private lateinit var subtypes: Array>
+ private val correlationDataProviders = mutableListOf(MessageOriginProvider())
fun registerAggregateFactory(aggregateFactory: AggregateFactory) = apply { this.aggregateFactory = aggregateFactory }
fun registerAnnotatedCommandHandler(annotatedCommandHandler: Any) = apply { this.annotatedCommandHandler.add(annotatedCommandHandler) }
@@ -85,6 +89,10 @@ class AggregateTestFixtureBuilder(private val aggregateType: Class) {
fun withSubtypes(vararg subtypes: Class) = withSubtypes(subtypes.asList())
fun withSubtypes(subtypes: Collection>) = apply { this.subtypes = subtypes.toTypedArray() }
+ fun registerCorrelationDataProvider(correlationDataProvider: CorrelationDataProvider) = apply {
+ this.correlationDataProviders.add(correlationDataProvider)
+ }
+
fun build(): AggregateTestFixture {
val fixture = AggregateTestFixture(aggregateType)
@@ -112,6 +120,11 @@ class AggregateTestFixtureBuilder(private val aggregateType: Class) {
if (this::subtypes.isInitialized) fixture.withSubtypes(*this.subtypes)
reportIllegalStateChange?.let { fixture.setReportIllegalStateChange(it) }
+ if (correlationDataProviders.isNotEmpty()) {
+ fixture.commandBus.registerHandlerInterceptor(
+ CorrelationDataInterceptor(*(correlationDataProviders.toTypedArray()))
+ )
+ }
return fixture
}
}
diff --git a/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureContext.kt b/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureContext.kt
index 65df864..ff74910 100644
--- a/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureContext.kt
+++ b/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureContext.kt
@@ -1,5 +1,7 @@
package io.holixon.axon.testing.jgiven.aggregate
+import io.holixon.axon.testing.jgiven.aggregate.AggregateTestFixtureReflection.aggregateIdentifier
+import io.holixon.axon.testing.jgiven.aggregate.AggregateTestFixtureReflection.buildResultValidator
import org.axonframework.test.aggregate.AggregateTestFixture
import org.axonframework.test.aggregate.ResultValidator
import org.axonframework.test.aggregate.TestExecutor
@@ -13,7 +15,7 @@ internal class AggregateTestFixtureContext(
constructor(fixture: AggregateTestFixture) : this(
fixture,
fixture.givenNoPriorActivity(),
- AggregateTestFixtureExt.buildResultValidator(fixture)
+ buildResultValidator(fixture)
)
fun checkInitialized() {
@@ -25,4 +27,6 @@ internal class AggregateTestFixtureContext(
fun isInitialized() = fixture != null && testExecutor != null && resultValidator != null
var isFirstGiven = true
+
+ val aggregateIdentifier: String? get() = fixture?.aggregateIdentifier
}
diff --git a/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureExt.kt b/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureExt.kt
deleted file mode 100644
index a630945..0000000
--- a/extension/jgiven/core/src/main/kotlin/aggregate/AggregateTestFixtureExt.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.holixon.axon.testing.jgiven.aggregate
-
-import org.axonframework.test.aggregate.AggregateTestFixture
-import org.axonframework.test.aggregate.ResultValidator
-import java.lang.reflect.Method
-
-object AggregateTestFixtureExt {
-
- private val method_buildResultValidator: Method = AggregateTestFixture::class.java.getDeclaredMethod("buildResultValidator").apply {
- isAccessible = true
- }
-
- @Suppress("UNCHECKED_CAST")
- fun buildResultValidator(fixture:AggregateTestFixture) = method_buildResultValidator.invoke(fixture) as ResultValidator
-}
diff --git a/extension/jgiven/core/src/main/kotlin/aggregate/_reflection.kt b/extension/jgiven/core/src/main/kotlin/aggregate/_reflection.kt
new file mode 100644
index 0000000..a66c717
--- /dev/null
+++ b/extension/jgiven/core/src/main/kotlin/aggregate/_reflection.kt
@@ -0,0 +1,19 @@
+package io.holixon.axon.testing.jgiven.aggregate
+
+import io.holixon.axon.testing.jgiven.AxonJGiven.getDeclaredAccessibleField
+import io.holixon.axon.testing.jgiven.AxonJGiven.getDeclaredAccessibleMethod
+import org.axonframework.test.aggregate.AggregateTestFixture
+import org.axonframework.test.aggregate.ResultValidator
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+
+internal object AggregateTestFixtureReflection {
+
+ private val method_buildResultValidator: Method = AggregateTestFixture::class.java.getDeclaredAccessibleMethod("buildResultValidator")
+ private val field_aggregateIdentifier: Field = AggregateTestFixture::class.java.getDeclaredAccessibleField("aggregateIdentifier")
+
+ @Suppress("UNCHECKED_CAST")
+ fun buildResultValidator(fixture: AggregateTestFixture): ResultValidator = method_buildResultValidator.invoke(fixture) as ResultValidator
+
+ val AggregateTestFixture.aggregateIdentifier: String? get() = field_aggregateIdentifier.get(this) as String?
+}
diff --git a/extension/jgiven/core/src/main/kotlin/saga/_reflection.kt b/extension/jgiven/core/src/main/kotlin/saga/_reflection.kt
index f304112..8f1fe70 100644
--- a/extension/jgiven/core/src/main/kotlin/saga/_reflection.kt
+++ b/extension/jgiven/core/src/main/kotlin/saga/_reflection.kt
@@ -1,5 +1,6 @@
package io.holixon.axon.testing.jgiven.saga
+import io.holixon.axon.testing.jgiven.AxonJGiven.getDeclaredAccessibleField
import io.holixon.axon.testing.jgiven.saga.SagaTestFixtureFields.fieldFixtureFixtureExecutionResult
import io.holixon.axon.testing.jgiven.saga.SagaTestFixtureFields.fieldSagaStore
import io.holixon.axon.testing.jgiven.saga.SagaTestFixtureFields.fieldSagaType
@@ -15,11 +16,6 @@ import java.lang.reflect.Field
object SagaTestFixtureFields {
private val CLASS = SagaTestFixture::class.java
- private fun Class<*>.getDeclaredAccessibleField(fieldName: String): Field = getDeclaredField(fieldName)
- .apply {
- isAccessible = true
- }
-
val fieldSagaStore: Field = CLASS.getDeclaredAccessibleField("sagaStore")
val fieldFixtureFixtureExecutionResult: Field = CLASS.getDeclaredAccessibleField("fixtureExecutionResult")
val fieldSagaType: Field = CLASS.getDeclaredAccessibleField("sagaType")
diff --git a/extension/jgiven/core/src/test/kotlin/AxonJGivenTestFixtures.kt b/extension/jgiven/core/src/test/kotlin/AxonJGivenTestFixtures.kt
index 3bec257..9c6b2c9 100644
--- a/extension/jgiven/core/src/test/kotlin/AxonJGivenTestFixtures.kt
+++ b/extension/jgiven/core/src/test/kotlin/AxonJGivenTestFixtures.kt
@@ -26,19 +26,14 @@ object AxonJGivenTestFixtures {
class DummyAggregate() {
- companion object {
-
- @CommandHandler
- @JvmStatic
- fun create(cmd: CreateDummyAggregate) = DummyAggregate().apply {
- AggregateLifecycle.apply(DummyAggregateCreated(cmd.id))
- }
-
- }
-
@AggregateIdentifier
lateinit var id: String
+ @CommandHandler
+ constructor(cmd: CreateDummyAggregate) : this() {
+ AggregateLifecycle.apply(DummyAggregateCreated(cmd.id))
+ }
+
@EventSourcingHandler
fun on(evt: DummyAggregateCreated) {
this.id = evt.id
diff --git a/extension/jgiven/core/src/test/kotlin/aggregate/AggregateTestFixtureBuilderTest.kt b/extension/jgiven/core/src/test/kotlin/aggregate/AggregateTestFixtureBuilderTest.kt
new file mode 100644
index 0000000..7a74217
--- /dev/null
+++ b/extension/jgiven/core/src/test/kotlin/aggregate/AggregateTestFixtureBuilderTest.kt
@@ -0,0 +1,74 @@
+package io.holixon.axon.testing.jgiven.aggregate
+
+import com.tngtech.jgiven.annotation.ProvidedScenarioState
+import io.holixon.axon.testing.jgiven.AxonJGiven
+import io.holixon.axon.testing.jgiven.AxonJGivenTestFixtures.AggregateFixtureScenarioTest
+import io.holixon.axon.testing.jgiven.AxonJGivenTestFixtures.CreateDummyAggregate
+import io.holixon.axon.testing.jgiven.AxonJGivenTestFixtures.DummyAggregate
+import io.holixon.axon.testing.jgiven.AxonJGivenTestFixtures.DummyAggregateCreated
+import io.toolisticon.testing.jgiven.GIVEN
+import io.toolisticon.testing.jgiven.THEN
+import io.toolisticon.testing.jgiven.WHEN
+import org.axonframework.commandhandling.CommandMessage
+import org.axonframework.messaging.MessageDispatchInterceptor
+import org.axonframework.messaging.correlation.SimpleCorrelationDataProvider
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import java.util.function.BiFunction
+
+internal class AggregateTestFixtureBuilderTest {
+
+ @Nested
+ inner class RegisterCorrelationDataProvider : AggregateFixtureScenarioTest() {
+
+ private fun addFooMetaToEveryCommand(value: String) = MessageDispatchInterceptor> {
+ BiFunction { _, cmd -> cmd.andMetaData(mapOf("foo" to value)) }
+ }
+
+ @ProvidedScenarioState
+ val fixture = AxonJGiven.aggregateTestFixtureBuilder()
+ .registerCommandDispatchInterceptor(addFooMetaToEveryCommand("bar"))
+ .registerCorrelationDataProvider(SimpleCorrelationDataProvider("foo"))
+ .build()
+
+ @Test
+ fun `metaData foo=bar is propagated to event`() {
+ GIVEN.noPriorActivity()
+
+ WHEN.command(CreateDummyAggregate("1"))
+
+ THEN
+ .expectEventWithMetaData(DummyAggregateCreated("1"), "foo" to "bar")
+ }
+ }
+
+
+ @Nested
+ inner class EmptyFixture : AggregateFixtureScenarioTest() {
+
+ @ProvidedScenarioState
+ val fixture = AxonJGiven.aggregateTestFixtureBuilder().build()
+
+ @Test
+ fun `create aggregate`() {
+ GIVEN
+ .noPriorActivity()
+
+ WHEN
+ .command(CreateDummyAggregate("1"))
+
+ THEN
+ .expectEvent(DummyAggregateCreated("1"))
+ }
+
+ @Test
+ fun `just no events`() {
+ GIVEN
+ .noPriorActivity()
+
+ THEN
+ .expectNoEvents()
+ }
+ }
+
+}
diff --git a/extension/jgiven/core/src/test/kotlin/aggregate/DummyAggregateTest.kt b/extension/jgiven/core/src/test/kotlin/aggregate/DummyAggregateTest.kt
deleted file mode 100644
index 414c9af..0000000
--- a/extension/jgiven/core/src/test/kotlin/aggregate/DummyAggregateTest.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.holixon.axon.testing.jgiven.aggregate
-
-import com.tngtech.jgiven.annotation.ProvidedScenarioState
-import io.holixon.axon.testing.jgiven.AxonJGiven.aggregateTestFixtureBuilder
-import io.holixon.axon.testing.jgiven.AxonJGivenTestFixtures
-import io.holixon.axon.testing.jgiven.AxonJGivenTestFixtures.CreateDummyAggregate
-import io.holixon.axon.testing.jgiven.AxonJGivenTestFixtures.DummyAggregate
-import io.holixon.axon.testing.jgiven.AxonJGivenTestFixtures.DummyAggregateCreated
-import io.toolisticon.testing.jgiven.GIVEN
-import io.toolisticon.testing.jgiven.THEN
-import io.toolisticon.testing.jgiven.WHEN
-import org.junit.jupiter.api.Test
-
-class DummyAggregateTest : AxonJGivenTestFixtures.AggregateFixtureScenarioTest() {
-
- @ProvidedScenarioState
- val fixture = aggregateTestFixtureBuilder()
- .build()
-
- @Test
- fun `create aggregate`() {
- GIVEN
- .noPriorActivity()
-
- WHEN
- .command(CreateDummyAggregate("1"))
-
- THEN
- .expectEvent(DummyAggregateCreated("1"))
- }
-
- @Test
- fun `just no events`() {
- GIVEN
- .noPriorActivity()
-
- THEN
- .expectNoEvents()
- }
-}