be smart when testing your akka code
TRANSCRIPT
Blood, sweat and tears
... or be smart when testing your Akka code
• PHP, NodeJS, AngularJS, Python, Java, Scala;
• Living in the Netherlands, working at
• Developing release automation product: XL Release.
About me
github:mkotsur/restito
• TDD is great when done properly!
• Reactive complexity;
• Learned a lot during last 4 months.
Why testing?
• Tests;
• Better tests;
• Problem-less tests.
• Concurrency, parallelism, state.
• Not just messages;
• Willing to help you with the TestKit.
Not only messages
Not only messages
Not only messages
Not only messages
Not only messages
object IncrementorActorMessages {
case class Inc(i: Int)
}
class IncrementorActor extends Actor {
var sum: Int = 0
override def receive: Receive = { case Inc(i) => sum = sum + i }
}
Sync unit-testing
• Works with `CallingThreadDispatcher`;
• Supports either message-sending style, or direct invocations.
class IncrementorActorTest extends TestKit(ActorSystem(“test-system")) {
...
}
it("should have sum = 0 by default") { val actorRef = TestActorRef[IncrementorActor]
actorRef.underlyingActor.sum shouldEqual 0 }
it("should increment on new messages") { val actorRef = TestActorRef[IncrementorActor]
actorRef ! Inc(2) actorRef.underlyingActor.sum shouldEqual 2
actorRef.underlyingActor.receive(Inc(3)) actorRef.underlyingActor.sum shouldEqual 5 }
class LazyIncrementorActor extends Actor {
var sum: Int = 0
override def receive: Receive = { case Inc(i) => Future { Thread.sleep(100) sum = sum + i } }
}
Not good enough
Not only messages
object IncrementorActorMessages {
case class Inc(i: Int)
case object Result
}
class IncrementorActor extends Actor {
var sum: Int = 0
override def receive: Receive = {
case Inc(i) => sum = sum + i
case Result => sender() ! sum }
}
New message
it("should have sum = 0 by default") { val actorRef = system .actorOf(Props(classOf[IncrementorActor]))
val probe = TestProbe() actorRef.tell(Result, probe.ref)
probe.expectMsg(0) }
Using TestProbe
it("should have sum = 0 by default") { val actorRef = system .actorOf(Props(classOf[IncrementorActor]))
actorRef ! Result
expectMsg(0) }
Using TestProbe
... with ImplicitSender
it("should increment on new messages") { val actorRef = system .actorOf(Props(classOf[IncrementorActor]))
actorRef ! Inc(2) actorRef ! Result
expectMsg(2)
actorRef ! Inc(3) actorRef ! Result
expectMsg(5) }
Using TestProbe
expectMsg*
def expectMsg[T](d: Duration, msg: T): T
def expectMsgPF[T](d: Duration) (pf: PartialFunction[Any, T]): T
def expectMsgClass[T](d: Duration, c: Class[T]): T
def expectNoMsg(d: Duration) // blocks
Fishing
def receiveN(n: Int, d: Duration): Seq[AnyRef]
def receiveWhile[T](max: Duration, idle: Duration, n: Int) (pf: PartialFunction[Any, T]): Seq[T]
def fishForMessage(max: Duration, hint: String) (pf: PartialFunction[Any, Boolean]): Any
Awaits
def awaitCond(p: => Boolean, max: Duration, interval: Duration)
def awaitAssert(a: => Any, max: Duration, interval: Duration)
// from ScalaTest
def eventually[T](fun: => T) (implicit config: PatienceConfig): T
Ignores
def ignoreMsg(pf: PartialFunction[AnyRef, Boolean])
def ignoreNoMsg()
Death watching
val probe = TestProbe()
probe watch target target ! PoisonPill
probe.expectTerminated(target)
Test probes as dependencies
class HappyParentActor(childMaker: ActorRefFactory => ActorRef) extends Actor {
val child: ActorRef = childMaker(context)
override def receive: Receive = { case msg => child.forward(msg) }
}
Event filter
class MyActor extends Actor with ActorLogging {
override def receive: Receive = {
case DoSideEffect => log.info("Hello World!") } }
Event filter
EventFilter.info( message = "Hello World!", occurrences = 1 ).intercept { myActor ! DoSomething }
akka.loggers = ["akka.testkit.TestEventListener"]
Supervision
class MyActor extends Actor with ActorLogging {
override def supervisorStrategy: Unit = OneForOneStrategy() { case _: FatalException => SupervisorStrategy.Escalate case _: ShitHappensException => SupervisorStrategy.Restart }
}
Supervision
val actorRef = TestActorRef[MyActor](MyActor.props()) val pf = actorRef.underlyingActor .supervisorStrategy.decider
pf(new FatalException()) should be (Escalate) pf(new ShitHappensException()) should be (Restart)
• Tests;
• Better tests;
• Problem-less tests.
TestBase
class MyActorTest extends TestKit(ActorSystem("test-system")) with FunSpecLike {
override protected def afterAll(): Unit = { super.afterAll() system.shutdown() system.awaitTermination() } }
TestBaseclass MyActorTest extends TestKit(ActorSystem("my-system")) with AkkaTestBase { ... }
trait AkkaTestBase extends BeforeAndAfterAll with FunSpecLike { this: TestKit with Suite =>
override protected def afterAll() { super.afterAll() system.shutdown() system.awaitTermination() } }
TestBase: v2class MyActorTest extends AkkaTestBase { ... }
abstract class AkkaTestBase extends TestKit(ActorSystem("test-system")) with FunSpecLike with BeforeAndAfterAll {
override protected def afterAll() { super.afterAll() system.shutdown() } }
Timeouts
akka.test.single-expect-default = 3 seconds
akka.test.timefactor = 10
Settings extensionclass Settings(...) extends Extension {
object Jdbc { val Driver = config.getString("app.jdbc.driver") val Url = config.getString("app.jdbc.url") }
}
Settings extension
class MyActor extends Actor { val settings = Settings(context.system) val connection = client.connect( settings.Jdbc.Driver, settings.Jdbc.Url ) }
Settings extension
val config = ConfigFactory.parseString(""" app.jdbc.driver = "org.h2.Driver" app.jdbc.url = "jdbc:h2:mem:repository" """)
val system = ActorSystem("testsystem", config)
Dynamic actors
case class Identify(messageId: Any)
case class ActorIdentity( correlationId: Any, ref: Option[ActorRef] )
• Continuous delivery;
• No dedicated QA engineers;
• 500+ Jenkins jobs.
We depend on tests
• Tests;
• Better tests;
• Problem-less tests.
Be careful with mocking.
Prefer checking messages over checking side-effects.
Single responsibility principle.
Run your tests on slow VM and different OS.
Extract *all* timeouts into conf files. So that you can easily override them on Jenkins.
Don’t hesitate to rewrite test.
Don't hesitate to rewrite application code.
Don’t trust assertion errors, check logs.
Base your decisions on historical data.
Be humane and spread the word.
Questions?
github:mkotsur/akka-smart-testing