Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Akka Testkit Patterns

Actors testing is different from what you are used to. First, you have messages instead of calls, second, you have to deal with concurrency and all the consequences that it brings with it:
* Thread.sleeps in tests;
* Flakiness;
* Green on laptop / red on jenkins;
* Missed test cases.

Fortunately Akka provides a TestKit which helps to avoid all these things when used properly. Let's take out and inspect tools from this kit and learn couple of useful patterns.

  • Login to see the comments

Akka Testkit Patterns

  1. 1. Reactive Amsterdam meetup 12.07.2016 Mike Kotsur
 @sotomajor_ua Actor testing patterns with TestKit
  2. 2. • Testing • Testing Akka applications • Better testing Akka applications • Testing Akka applications with less problems Agenda
  3. 3. TDD
  4. 4. A provocative talk and blog posts has led to a conversation where we aim to understand each others' views and experiences. http://martinfowler.com/articles/is-tdd-dead/ Is TDD dead? M. Fowler K. Beck David Heinemeier Hansson
  5. 5. Programmers at work maintaining a Ruby on Rails testless application Eero Järnefelt, Oil on canvas, 1893
  6. 6. A test
  7. 7. An async test
  8. 8. An async test
  9. 9. • Retry the assertion many times; • The worker tells when the work is done. 2 conceptual solutions
  10. 10. • Retry the assertion many times; • The worker tells when the work is done. 2 conceptual solutions questions How many times? How long to wait?
  11. 11. • Not just actors and messages! • Willing to help you with the test kit. So, what about Akka?
  12. 12. 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
 }
 } // TODO: test this
  13. 13. class IncrementorActorTest
 extends TestKit(ActorSystem("test-system")) {
 
 
 // it(“should …”) { … }
 
 
 } // We need an actor system
  14. 14. it("should have sum = 0 by default") {
 
 val actorRef = TestActorRef[IncrementorActor]
 actorRef.underlyingActor.sum shouldEqual 0
 
 } // Uses the same thread Has a real type!
  15. 15. it("should increment on new messages1") {
 
 val actorRef = TestActorRef[IncrementorActor]
 
 actorRef ! Inc(2)
 actorRef.underlyingActor.sum shouldEqual 2
 
 actorRef.underlyingActor.receive(Inc(3))
 actorRef.underlyingActor.sum shouldEqual 5
 
 } 
 it("should increment on new messages2") {
 
 val actorRef = TestActorRef[IncrementorActor]
 
 actorRef ! Inc(2)
 actorRef.underlyingActor.sum shouldEqual 2
 
 actorRef.underlyingActor.receive(Inc(3))
 actorRef.underlyingActor.sum shouldEqual 5
 
 } // “Sending” messages Style 1 Style 2
  16. 16. class LazyIncrementorActor extends Actor {
 var sum: Int = 0
 
 override def receive: Receive = {
 case Inc(i) =>
 Future {
 Thread.sleep(100)
 sum = sum + i
 }
 }
 
 }
  17. 17. 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
  18. 18. // ... with ImplicitSender it("should have sum = 0 by default") { val actorRef = system .actorOf(Props(classOf[IncrementorActor])) actorRef ! Result expectMsg(0) } TestKit trait
  19. 19. 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) }
  20. 20. TestKit
  21. 21. 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 // expectMsg*
  22. 22. 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 // fishing*
  23. 23. 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 // await*
  24. 24. def ignoreMsg(pf: PartialFunction[AnyRef, Boolean]) def ignoreNoMsg() // ignore*
  25. 25. val probe = TestProbe() probe watch target target ! PoisonPill probe.expectTerminated(target) // death watch Somewhere in the app code
  26. 26. • context.actorOf() • context.parent • context.child(name) Dependency injection
  27. 27. • Use props; • Use childMaker: ActorRefFactory => ActorRef; • Use a fabricated parent. Dependency Injection
  28. 28. class MyActor extends Actor with ActorLogging { override def receive: Receive = { case DoSideEffect => log.info("Hello World!") } } // event filter
  29. 29. // akka.loggers = ["akka.testkit.TestEventListener"] EventFilter.info( message = "Hello World!”, occurrences = 1 ).intercept { myActor ! DoSomething } // event filter
  30. 30. class MyActor extends Actor with ActorLogging { override def supervisorStrategy: Unit = OneForOneStrategy() { case _: FatalException => SupervisorStrategy.Escalate case _: ShitHappensException => SupervisorStrategy.Restart } } // supervision
  31. 31. // unit-test style val actorRef = TestActorRef[MyActor](MyActor.props()) val pf = actorRef.underlyingActor .supervisorStrategy.decider pf(new FatalException()) should be (Escalate) pf(new ShitHappensException()) should be (Restart) // supervision
  32. 32. // coding time
  33. 33. // better tests
  34. 34. flaky /ˈfleɪki/ adjective, informal (of a device or software) prone to break down; unreliable.
  35. 35. class MyActorTest extends TestKit(ActorSystem("test-system")) with FunSpecLike { override protected def afterAll(): Unit = { super.afterAll() system.shutdown() system.awaitTermination() } } // shutting down the actor system
  36. 36. trait AkkaTestBase extends BeforeAndAfterAll with FunSpecLike { this: TestKit with Suite => override protected def afterAll() { super.afterAll() system.shutdown() system.awaitTermination() } } // shutting down the actor system
  37. 37. akka.test.single-expect-default = 3 seconds akka.test.timefactor = 10 // timeouts import scala.concurrent.duration._ import akka.testkit._ 10.milliseconds.dilated
  38. 38. class Settings(...) extends Extension { object Jdbc { val Driver = config.getString("app.jdbc.driver") val Url = config.getString("app.jdbc.url") } } // settings extension
  39. 39. // test val config = ConfigFactory.parseString(""" app.jdbc.driver = "org.h2.Driver" app.jdbc.url = "jdbc:h2:mem:repository" """) val system = ActorSystem("testsystem", config) // app class MyActor extends Actor { val settings = Settings(context.system) val connection = client.connect( settings.Jdbc.Driver, settings.Jdbc.Url ) } // settings extension
  40. 40. case class Identify(messageId: Any) case class ActorIdentity( correlationId: Any, ref: Option[ActorRef] ) // dynamic actors
  41. 41. // Beware of unique name lifecycles and scopes…
  42. 42. // Prefer checking messages over checking side-effects.
  43. 43. // Single responsibility principle.
  44. 44. // Run your tests on slow VM and different OS.
  45. 45. // Extract *all* timeouts into conf files. So that you can easily override them -Dakka.test.someImportantProperty=3000
  46. 46. // Don’t hesitate to rewrite a test, or the code.
  47. 47. // Don’t trust assertion errors, check logs.
  48. 48. // Base your decisions on historical data.
  49. 49. // QA?

×