测试 akka actor 在不保证顺序的情况下接收一组不同类型的消息

Test that an akka actor receives a set of different types of messages without guaranteeing order

TL;DR:

我不知道如何在不保证消息顺序的情况下测试一个 Akka actor 接收一组不同类型的消息。

规格:

我正在测试一些域事件是否发布到 akka.event.EventStream。为此,我订阅了 TestProbe 所有 DomainEvent subclasses:

val eventBusTestSubscriber = TestProbe()(actorSystem)
actorSystem.eventStream.subscribe(eventBusTestSubscriber.ref, classOf[DomainEvent])

这样,我可以测试 单个域事件到达 EventStream 而无需考虑其他可能的事件(避免脆弱测试):

规格:

shouldPublishDomainEvent {
  event: WinterHasArrivedDomainEvent =>
    event.isReal shouldBe true
    event.year shouldBe expectedYear
}

辅助特征:

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)
  }
}

我也有一些测试我应该接收一组相同类型的事件保证顺序而不考虑其他可能的事件(避免脆弱测试):

规格:

val someoneDiedEventAsserter: SomeoneDiedDomainEvent => Unit = { event =>
  event.isReal shouldBe false
  event.episodeId shouldBe episodeId
}

val someoneDiedEventIdExtractor = (event: SomeoneDiedDomainEvent) => event.characterId

shouldPublishDomainEventsOfType(someoneDiedEventAsserter, someoneDiedEventIdExtractor)(characterIdsToDie)

辅助特征:

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)
    }
  }
}

现在的问题是用例,我必须在该用例中测试我正在发布一组具有不同类型且没有顺序保证的事件

我不知道如何解决的问题是,在 shouldPublishDomainEventsOfType 的情况下,我有一个推断的 EventType 它为我提供了类型以便执行与以下内容相关的特定断言eventAsserter: EventType => Unit 中的这种非常具体的类型。但是由于我有不同的特定类型的事件,我不知道如何指定它们的类型等等。

我尝试了一种基于包含断言函数的案例 class 的方法,但问题是一样的,我有点卡住了:

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)
      }
    }
  }
}

谢谢!

感谢 Artur Soler 解决了 :)

这里有解决方案,以防对任何人有帮助:

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)
    }
  }
}