Pergunta

I am trying to set up some unit tests with the Play framework. A lot of my logic is built into scheduled akka actors that go off and gather data in the background. My problem is that I can't figure out how to unit test them. I literally have no clue how to approach it. I'm trying to use akka-testkit, but I'm basically flailing around. Does anyone have any suggestions on how to even approach it? Examples would be incredibly useful. This is an example of the abomination I am currently working with:

package test

import org.specs2.mutable._
import controllers.Scanner
import java.util.UUID
import org.joda.time.DateTime
import akka.testkit.TestActorRef
import play.api.Logger
import play.api.test.{FakeApplication, TestServer}
import models.PSqlEnum

class ScannerTest extends Specification {
  val appId = UUID.randomUUID()
  val app = models.App(appId, "TestApp", "TestServer", "TestComponent", "Test Description", DateTime.now(),
                       DateTime.now(), true, 3, 60, PSqlEnum("scanType", "mandatory"), "http://localhost")
  val rules = <Rule name="DivisionDataIsAvailable" elementsToCheck="DivisionDataIsAvailable"
                    ruleType="is, true, yellow" />
              <Rule name="DivisionDataLoadIsHealthy" elementsToCheck="DivisionDataLoadIsHealthy"
                    ruleType="is, true, red" />;

  "Scanner" should {
    "test something" in {
      val fakeApp = TestServer(3333)
      fakeApp.start()
      implicit val actorSystem = play.api.libs.concurrent.Akka.system(fakeApp.application)
      val scanner = TestActorRef(new Scanner(app, rules)).underlyingActor
      Logger.warn(scanner.getResponseFromWebService.toString)
      fakeApp.stop()
      1 === 1
    }
  }
}

This is obviously not really testing anything. I am basically trying to get it to get through to the 1 === 1 at this point just to see if I can get the runtime errors to stop. The errors this code is generating are these:

INFO  - Starting application default Akka system.
[info] ScannerTest
[info] Scanner should
[info] ! test something
[error]     ThrowableException: akka.actor.LocalActorRef.<init>(Lakka/actor/ActorSystemImpl;Lakka/actor/Props;Lakka/actor/InternalActorRef;Lakka/actor/ActorPath;)V (TestActorRef.scala:21)
[error] akka.testkit.TestActorRef.<init>(TestActorRef.scala:21)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:135)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:132)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:125)
[error] test.ScannerTest$$anonfun$1$$anonfun$apply$3.apply(ScannerTest.scala:27)
[error] test.ScannerTest$$anonfun$1$$anonfun$apply$3.apply(ScannerTest.scala:23)
[error] akka.actor.LocalActorRef.<init>(Lakka/actor/ActorSystemImpl;Lakka/actor/Props;Lakka/actor/InternalActorRef;Lakka/actor/ActorPath;)V
[error] akka.testkit.TestActorRef.<init>(TestActorRef.scala:21)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:135)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:132)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:125)
[error] test.ScannerTest$$anonfun$1$$anonfun$apply$3.apply(ScannerTest.scala:27)
[error] test.ScannerTest$$anonfun$1$$anonfun$apply$3.apply(ScannerTest.scala:23)
[info] Total for specification ScannerTest
[info] Finished in 86 ms
[info] 1 example, 0 failure, 1 error
[info] test.ScannerTest

I believe that I need to create a FakeApplication and use that FakeApplication's Akka.system; however, I am not sure how to do it. To be honest, I am not even sure if that is the correct approach. If I could just generate a generic Akka.system and have that work I'd be ecstatic. If anyone has any ideas on how to tackle this I'd be really appreciative.

Foi útil?

Solução

Okay, I figured it out. Make sure you are using the correct version of akka-testkit. In Play 2.2.0 I was trying to use akka 2.2.M3. Obviously, that doesn't work. I had to put the correct dependencies in my Build.scala, which ended up being this:

"com.typesafe.akka" %% "akka-testkit" % "2.2.0" % "test"

My actual test code looks like this:

package test

import org.specs2.mutable._
import controllers.Scanner
import java.util.UUID
import org.joda.time.DateTime
import akka.testkit.TestActorRef
import play.api.Logger
import models.PSqlEnum
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import scala.concurrent.ExecutionContext.Implicits.global

class ScannerTest extends Specification {
  val appId = UUID.randomUUID()
  val app = models.App(appId, "TestApp", "TestServer", "TestComponent", "Test Description", DateTime.now(),
                       DateTime.now(), true, 3, 60, PSqlEnum("scanType", "mandatory"), "http://localhost")
  val rules = <Rule name="DivisionDataIsAvailable" elementsToCheck="DivisionDataIsAvailable"
                    ruleType="is, true, yellow" />
              <Rule name="DivisionDataLoadIsHealthy" elementsToCheck="DivisionDataLoadIsHealthy"
                    ruleType="is, true, red" />;


  "Scanner" should {
    "test something" in {
      implicit val actorSystem = ActorSystem("testActorSystem", ConfigFactory.load())
      val scanner = TestActorRef(new Scanner(app, rules)).underlyingActor
      val response = scanner.getResponseFromWebService
      response onSuccess {
        case result => Logger.warn(result.toString)
      }
      response onFailure {
        case error => Logger.warn(error.toString)
      }
      1 === 1
    }
  }
}

Obviously again, this test isn't really doing anything. The actual test being evaluated is 1 === 1. It does print out to the log now though which means I can go back and verify datatypes and the payload of the response, and then build some actual Unit Tests. I hope someone finds this useful. Those error messages in the original question are caused by the akka-testkit dependency not being the same version as Akka though, which might be useful for someone.

Outras dicas

I'm not using spec2 or Mockito but here is what I'm doing to unit test Akka actors in Play 2.2.1:

import org.specs2.mutable.Specification
import scala.concurrent.duration._
import org.scalatest.concurrent._
import akka.testkit._
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.{FlatSpec, BeforeAndAfterAll}
import akka.actor.{Props, ActorSystem}
import akka.pattern.ask
import akka.util.Timeout
import scala.util.{Failure, Success}
import model.BlacklistEntry
import scala.concurrent.{Future, Promise, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import play.api.test.FakeApplication
import model.BlacklistEntryImpl


class LicenceBlackListSpec(_system: ActorSystem) extends TestKit(_system) with ImplicitSender with ShouldMatchers with FlatSpec with BeforeAndAfterAll {
  play.api.Play.start(FakeApplication())

  import akka.testkit.TestKit._
  def this() = this( ActorSystem("LicenceBlackListSpec") )

  override def afterAll: Unit = {
    system.shutdown()
    system.awaitTermination(10.seconds)
  }

  implicit val timeout = Timeout(10 seconds)
  val blacklistRef = TestActorRef(Props[LicenceBlackList])

  "An LicenceBlackList Actor" should "be able to create a new blacklist entry" in {
    blacklistRef ! CreateEntry(BlacklistEntryImpl("NEW_KEY",1000,"Test creation"))

    val expected: BlacklistEntry = BlacklistEntryImpl("NEW_KEY", 1000 ,"Test creation")
    expectMsg( expected )
  }
}

You'll need to include the scalatest lib as a dependency as well akka test kit:

"org.scalatest" % "scalatest_2.10" % "1.9.1"

Hope this will help.

I don't use ActorSystem("testActorSystem" ) , I have tryed to allow play framework to use it's usual akka plugin. This approach has folowing benefits: I can use play.api.libs.concurrent.Akka.system in all code, I can have extra config options that understand booth play and akka code parts .

  class BaseActorTester (_app: Application) extends akka.testkit.TestKit(play.api.libs.concurrent.Akka.system(_app)) with FunSuiteLike with BeforeAndAfterAll {

   def this() = this(FakeApplication(additionalConfiguration=Map("currency.db"->"airando-test")))
   implicit val app: Application = _app
   implicit val ec = play.api.libs.concurrent.Akka.system.dispatcher

   override def beforeAll {
     Play.start(app)
   }
   // play plugin do it itself ??
   //override def afterAll {
   //  akka.testkit.TestKit.shutdownActorSystem(system)
   //}
   ...
  }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top