Akka Actor 测试参考指南
引言
Akka Actor的测试主要依赖ActorTestKit及其衍生的测试工具,并以ScalaTest为主要测试套件。
本文从Akka Platform Guide的测试代码中简单整理了Akka Actor测试相关的一些内容,供编写测试时参考。
ScalaTestWithActorTestKit 🔗
ScalaTestWithActorTestKit
是测试的基础,用于模拟一个完整的ActorSystem。该测试套件将会在测试通过时自动关闭,或者是在测试失败后使用trait BeforeAndAfterAll
手动关闭。
ScalaTestWithActorTestKit
默认将加载application-test.conf
中的配置,或者使用Akka自带的reference.conf
,而不会是项目中使用的application.conf
,所以通常需要自己定义配置的加载位置。
BehaviorTestKit 🔗
BehaviorTestKit
是用于同步测试的工具(异步测试应使用ActorTestKit
),可以用它创建子Actor、监测另一个Actor、检测发生Effect的情况,是最基础的Actor测试工具。
// Get the child inbox for the child ActorRef, or fail if there is no such child.
abstract def childInbox[U](child: typed.ActorRef[U]): TestInbox[U]
// Get the child inbox for the child with the given name, or fail if there is no child with the given name spawned
abstract def childInbox[U](name: String): TestInbox[U]
// Get the akka.actor.typed.Behavior testkit for the given child akka.actor.typed.ActorRef.
abstract def childTestKit[U](child: typed.ActorRef[U]): BehaviorTestKit[U]
// Clear the log entries
abstract def clearLog(): Unit
// The current behavior, can change any time run is called
abstract def currentBehavior: Behavior[T]
// Asserts that the oldest effect is the expectedEffect.
abstract def expectEffect(expectedEffect: Effect): Unit
// Asserts that the oldest effect matches the given partial function.
abstract def expectEffectPF[R](f: PartialFunction[Effect, R]): R
// Asserts that the oldest effect is of type T.
abstract def expectEffectType[E <: Effect](implicit classTag: ClassTag[E]): E
// Returns if there have been any effects.
abstract def hasEffects(): Boolean
// Is the current behavior alive or stopped
abstract def isAlive: Boolean
// Returns all the CapturedLogEvent issued by this behavior(s)
abstract def logEntries(): Seq[CapturedLogEvent]
// The receptionist inbox contains messages sent to system.receptionist
abstract def receptionistInbox(): TestInbox[Command]
// Requests all the effects.
abstract def retrieveAllEffects(): Seq[Effect]
// Requests the oldest Effect or akka.actor.testkit.typed.Effect.NoEffects if no effects have taken place.
abstract def retrieveEffect(): Effect
// Returns the current behavior as it was returned from processing the previous message.
abstract def returnedBehavior: Behavior[T]
// Send the message to the behavior and record any Effects
abstract def run(message: T): Unit
// Send the first message in the selfInbox to the behavior and run it, recording Effects.
abstract def runOne(): Unit
// The self inbox contains messages the behavior sent to context.self
abstract def selfInbox(): TestInbox[T]
// Send the signal to the behavior and record any Effects
abstract def signal(signal: Signal): Unit
EventSourcedBehaviorTestKit 🔗
EventSourcedBehaviorTestKit
是对应于EventSourcedBehavior
的测试工具,可以测试command触发的event,还可以测试event被触发后产生的state的变化情况,并获得执行command后回复的reply。
@ApiMayChange
@DoNotInherit trait EventSourcedBehaviorTestKit[Command, Event, State] {
import EventSourcedBehaviorTestKit._
/**
* Run one command through the behavior. The returned result contains emitted events and the state
* after applying the events.
*/
def runCommand(command: Command): CommandResult[Command, Event, State]
/**
* Run one command with a `replyTo: ActorRef[R]` through the behavior. The returned result contains emitted events,
* the state after applying the events, and the reply.
*/
def runCommand[R](creator: ActorRef[R] => Command): CommandResultWithReply[Command, Event, State, R]
/**
* Retrieve the current state of the Behavior.
*/
def getState(): State
/**
* Restart the behavior, which will then recover from stored snapshot and events. Can be used for testing
* that the recovery is correct.
*/
def restart(): RestartResult[State]
/**
* Clears the in-memory journal and snapshot storage and restarts the behavior.
*/
def clear(): Unit
/**
* The underlying `PersistenceTestKit` for the in-memory journal and snapshot storage.
* Can be useful for advanced testing scenarios, such as simulating failures or
* populating the journal with events that are used for replay.
*/
def persistenceTestKit: PersistenceTestKit
}
CommandResultWithReply 🔗
CommandResult
与CommandResultWithReply
是BehaviorTestKit执行runCommand
后的结果。
// The command that was run.
def command: Command
// The first event.
def event: Event
// The first event as a given expected type.
def eventOfType[E <: Event](eventClass: Class[E]): E
// The events that were emitted by the command, and persisted.
def events: List[Event]
// true if no events were emitted by the command.
def hasNoEvents: Boolean
// The reply. It will throw AssertionError if there was no reply.
def reply: Reply
// The reply as a given expected type.
def replyOfType[R <: Reply](replyClass: Class[R]): R
// The state after applying the events.
def state: State
// The state as a given expected type.
def stateOfType[S <: State](stateClass: Class[S]): S
模板示例
import akka.pattern.StatusReply
object ShoppingCartSpec {
val config = ConfigFactory
.parseString(
"""
akka.actor.serialization-bindings {
"shopping.cart.CborSerializable" = jackson-cbor
}
""")
.withFallback(EventSourcedBehaviorTestKit.config)
}
class ShoppingCartSpec extends ScalaTestWithActorTestKit(ShoppingCartSpec.config)
with AnyWordSpecLike {
val cartId = "testCart"
val entity = EventSourcedBehaviorTestKit[Command, Event, State](system, ShoppingCart(cartId, "carts-0"))
val result: CommandResultWithReply = entity.runCommand[StatusReply[Summary]](AddItem("foo", 42, _))
val result: CommandResultWithReply = entity.runCommand[StatusReply[Summary]](replyTo => AddItem("foo", 42, replyTo))
result.reply.isSucess should ===(true)
result.reply.isError should ===(true)
result.reply should ===(StatusReply.Success(Summary(Map("foo" -> 42), checkedOut = false)))
result.event should ===(ItemAdded(cartId, "foo", 42))
result.event.asInstanceOf[CheckedOut].cartId should ===(cartId)
}
val sharedConfig: Config = ConfigFactory.load("integration-test.conf")
val testKit = ActorTestKit("IntegrationSpec", nodeConfig(grpcPort, managementPorts, managementPortIndex)
.withFallback(sharedConfig)
.resolve())
def system: ActorSystem[_] = testKit.system
val testNode = new TestNodeFixture(grpcPorts(0), managementPorts, 0)
val orderServiceProbe = testNode.testKit.createTestProbe[OrderRequest]()
val orderRequest = orderServiceProbe.expectMessageType[OrderRequest]
orderRequest.cartId should ===("cart-2")
orderRequest.items.head.itemId should ===("bar")
orderRequest.items.head.quantity should ===(18)
转载请注明出处及作者,谢谢!