You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何Mock Akka Actor进行单元测试?验证EventPublisher消息调用次数

没问题!针对你这个EventPublisher类的单元测试需求,我来给你详细讲讲怎么验证publish方法里给RabbitMQ Actor发送消息的次数刚好是一次。首先先把你的原代码格式化一下方便查看:

@Singleton 
class EventPublisher @Inject() (@Named("rabbit-mq-event-update-actor") rabbitControlActor: ActorRef) 
                               (implicit ctx: ExecutionContext) { 
  def publish(event: Event): Unit = { 
    logger.info("Publishing Event: {}", toJsObject(event), routingKey) 
    rabbitControlActor ! Message.topic(shipmentStatusUpdate, routingKey = "XXX") 
  } 
}

我们可以用两种常用的方式来实现这个测试:Akka TestKit的TestProbe(更贴合Akka生态)或者Mockito(通用mock工具),下面分别说明:

方法一:用Akka TestProbe做测试

TestProbe是Akka官方提供的测试工具,它可以模拟一个Actor,捕获所有发送给它的消息,非常适合测试Actor之间的交互。

1. 先准备依赖(以SBT为例)

确保你的build.sbt里添加了这些测试依赖:

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-testkit" % "2.6.20" % Test,
  "org.scalatest" %% "scalatest" % "3.2.15" % Test
)

2. 编写测试类

import akka.actor.ActorSystem
import akka.testkit.TestKit
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.ExecutionContext

class EventPublisherSpec extends TestKit(ActorSystem("EventPublisherSpec")) 
  with AnyWordSpecLike 
  with Matchers 
  with BeforeAndAfterAll {

  // 测试结束后关闭ActorSystem,避免资源泄漏
  override def afterAll(): Unit = {
    TestKit.shutdownActorSystem(system)
  }

  "EventPublisher" should {
    "send exactly one Message.topic to rabbitControlActor when publish is called" in {
      // 创建TestProbe来代替真实的rabbitControlActor
      val rabbitTestProbe = TestProbe()
      // 用系统的dispatcher作为隐式ExecutionContext
      implicit val ec: ExecutionContext = system.dispatcher
      
      // 实例化EventPublisher,把probe的ActorRef传进去
      val eventPublisher = new EventPublisher(rabbitTestProbe.ref)
      
      // 构造一个测试用的Event实例(根据你的Event接口/类实现具体逻辑)
      val testEvent = new Event { /* 这里可以模拟Event的字段或方法实现 */ }
      
      // 调用要测试的publish方法
      eventPublisher.publish(testEvent)
      
      // 验证probe收到了且仅收到一次目标消息
      // 方式1:直接等待并匹配消息
      rabbitTestProbe.expectMsg(Message.topic(shipmentStatusUpdate, routingKey = "XXX"))
      // 方式2:确认消息数量为1(更严谨)
      val receivedMessages = rabbitTestProbe.receiveN(1)
      receivedMessages should contain only Message.topic(shipmentStatusUpdate, routingKey = "XXX")
    }
  }
}

方法二:用Mockito做测试

如果你更习惯用Mockito来mock对象,也可以用这种方式,核心是mockActorRef并验证它的消息发送方法被调用的次数。

1. 添加Mockito依赖

在build.sbt里加上:

libraryDependencies += "org.mockito" %% "mockito-scala" % "1.17.12" % Test

2. 编写测试类

import org.mockito.Mockito._
import org.scalatest.BeforeAndAfterEach
import org.scalatest.wordspec.AnyWordSpecLike
import org.scalatest.matchers.should.Matchers
import org.mockito.ArgumentMatchers._
import scala.concurrent.ExecutionContext

class EventPublisherMockitoSpec extends AnyWordSpecLike with Matchers with BeforeAndAfterEach {
  // mock一个ActorRef实例
  private val mockRabbitActor = mock[ActorRef]
  // 用全局的ExecutionContext
  private implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global
  
  // 每次测试前重置mock,避免测试用例之间的干扰
  override def beforeEach(): Unit = {
    reset(mockRabbitActor)
  }

  "EventPublisher" should {
    "send Message.topic exactly once to rabbitControlActor" in {
      val eventPublisher = new EventPublisher(mockRabbitActor)
      val testEvent = new Event { /* 实现测试用的Event实例 */ }
      
      // 调用publish方法
      eventPublisher.publish(testEvent)
      
      // 验证mockActor的!方法被调用了一次,且参数是指定的Message.topic
      verify(mockRabbitActor, times(1)).!(eq(Message.topic(shipmentStatusUpdate, routingKey = "XXX")))(any[ActorRef])
    }
  }
}

几个注意点

  • 确保shipmentStatusUpdate和Event的测试实例能正确构造,如果这些是外部依赖,建议mock它们来隔离测试逻辑
  • 如果toJsObject方法有复杂业务逻辑,也可以mock它,避免测试被无关逻辑干扰
  • 测试时可以调整日志级别(比如设置为WARN),避免打印不必要的日志信息

内容的提问来源于stack exchange,提问作者Arun Gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 02:33:09