TL;DR:
I don't know how to test that an Akka actor receives a set of different types of messages without guaranteeing the order of the messages.
Specifics:
I'm testing out that some domain events are published to an akka.event.EventStream
. In order to do so, I've subscribed a TestProbe
to all the DomainEvent
subclasses:
val eventBusTestSubscriber = TestProbe()(actorSystem)
actorSystem.eventStream.subscribe(eventBusTestSubscriber.ref, classOf[DomainEvent])
This way, I can test out that a single domain event arrives to the EventStream
without taking into account other possible events (avoid fragile test):
Spec:
shouldPublishDomainEvent {
event: WinterHasArrivedDomainEvent =>
event.isReal shouldBe true
event.year shouldBe expectedYear
}
Helper trait:
def shouldPublishDomainEvent[EventType](eventAsserter: EventType => Unit)
(implicit gotContext: GotContextTest, classTag: ClassTag[EventType]): Unit = {
val receivedEvent = gotContext.eventBusTestSubscriber.receiveN(1).head
receivedEvent match {
case event: EventType =>
eventAsserter(event)
case _ =>
shouldPublishDomainEvent(eventAsserter)
}
}
I also have some test for the scenarios in which I should receive a set of events of the same type guaranteeing the order without taking into account other possible events (avoid fragile test):
Spec:
val someoneDiedEventAsserter: SomeoneDiedDomainEvent => Unit = { event =>
event.isReal shouldBe false
event.episodeId shouldBe episodeId
}
val someoneDiedEventIdExtractor = (event: SomeoneDiedDomainEvent) => event.characterId
shouldPublishDomainEventsOfType(someoneDiedEventAsserter, someoneDiedEventIdExtractor)(characterIdsToDie)
Helper trait:
def shouldPublishDomainEventsOfType[EventType, EventIdType](
eventAsserter: EventType => Unit,
eventIdExtractor: EventType => EventIdType
)(expectedEventIds: Set[EventIdType])
(implicit gotContext: GotContextTest, classTag: ClassTag[EventType]): Unit = {
if (expectedEventIds.nonEmpty) {
val receivedEvent = gotContext.eventBusTestSubscriber.receiveN(1).head
receivedEvent match {
case event: EventType =>
eventAsserter(event)
val receivedEventId = eventIdExtractor(event)
expectedEventIds should contain(receivedEventId)
shouldPublishDomainEventsOfType(eventAsserter, eventIdExtractor)(expectedEventIds - receivedEventId)
case _ =>
shouldPublishDomainEventsOfType(eventAsserter, eventIdExtractor)(expectedEventIds)
}
}
}
The problem now is with the use case in which I have to test that I'm publishing a set of events with different types and without the order guaranteed.
The problem I don't know how to solve is that, in the shouldPublishDomainEventsOfType
case, I have an inferred EventType
which provides me the type in order to perform the specific assertions related to this very specific type in the eventAsserter: EventType => Unit
. But since I have different specific types of events, I don't know how to specify their types and so on.
I've tried an approach based on a case class containing the assertion function, but the issue is the same and I'm a little bit stuck:
case class ExpectedDomainEvent[EventType <: DomainEvent](eventAsserter: EventType => Unit)
def shouldPublishDomainEvents[EventType](
expectedDomainEvents: Set[ExpectedDomainEvent]
)(implicit chatContext: ChatContextTest): Unit = {
if (expectedDomainEvents.nonEmpty) {
val receivedEvent = chatContext.eventBusTestSubscriber.receiveN(1).head
expectedDomainEvents.foreach { expectedDomainEvent =>
val wasTheReceivedEventExpected = Try(expectedDomainEvent.eventAsserter(receivedEvent))
if (wasTheReceivedEventExpected.isSuccess) {
shouldPublishDomainEvents(expectedDomainEvents - receivedEvent)
} else {
shouldPublishDomainEvents(expectedDomainEvents)
}
}
}
}
Thanks!
Solved thanks to Artur Soler :)
Here you have the solution just in case it helps anyone:
case class ExpectedDomainEventsOfType[EventType <: DomainEvent, EventId](
eventAsserter: EventType => Unit,
eventIdExtractor: EventType => EventId,
expectedEventIds: Set[EventId]
)(implicit expectedEventTypeClassTag: ClassTag[EventType]) {
def isOfEventType(event: DomainEvent): Boolean = expectedEventTypeClassTag.runtimeClass.isInstance(event)
def withReceivedEvent(event: DomainEvent): ExpectedDomainEventsOfType[EventType, EventId] = event match {
case asExpectedEventType: EventType =>
eventAsserter(asExpectedEventType)
val eventId = eventIdExtractor(asExpectedEventType)
expectedEventIds should contain(eventId)
copy(expectedEventIds = expectedEventIds - eventId)
}
}
def shouldPublishDomainEvents(
expectedEvents: Set[ExpectedDomainEventsOfType[_, _]]
)(implicit gotContext: GotContextTest): Unit = {
if (expectedEvents.nonEmpty) {
val receivedEvent = gotContext.eventBusTestSubscriber.receiveN(1).head.asInstanceOf[DomainEvent]
expectedEvents.find(expectedEventsOfType => expectedEventsOfType.isOfEventType(receivedEvent)) match {
case Some(expectedEventsOfReceivedType) =>
val expectedEventsWithoutTheReceived = expectedEventsOfReceivedType.withReceivedEvent(receivedEvent)
if (expectedEventsWithoutTheReceived.expectedEventIds.isEmpty) {
shouldPublishDomainEvents(expectedEvents - expectedEventsOfReceivedType)
} else {
shouldPublishDomainEvents(expectedEvents - expectedEventsOfReceivedType + expectedEventsWithoutTheReceived)
}
case None =>
shouldPublishDomainEvents(expectedEvents)
}
}
}