Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing actors using dependency injection in play framework 2.4.x

How do I test an actor that is created by dependency injection? In my application I can get an ActorRef by named injection:

public MyClass {
    @Inject
    @Named("ping")
    ActorRef mPingRef;
}

How do I get this reference in my tests?

This is my actor:

public class PingActor extends UntypedActor {
    @Inject
    public PingActor(Configuration configuration) {
         ... // Use config
    }


    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof Ping) {
            getSender().tell(new Pong(), getSelf());
        }
    }

    public static class Ping {}
    public static class Pong {}
}

I have configured my application with my own module:

public class MyModule extends AbstractModule implements AkkaGuiceSupport {
    private final Configuration mConfig;

    public MyModule(Environment environment, Configuration configuration){
        this.mConfig = configuration;
    }

    @Override
    protected void configure() {
        bindActor(PingActor.class, "ping");
    }
}

The module is enabled in application.conf:

play.modules.enabled += "com.my.package.MyModule"
like image 843
jorgenfb Avatar asked Sep 19 '15 15:09

jorgenfb


1 Answers

This solution is for PlayScala, but it should be the same mechanism for your PlayJava:

So I got my GuiceModule:

class CommonModule extends AbstractModule with AkkaGuiceSupport {
  override def configure(): Unit = {
    bindActor[SomeActor]("actor-name")
  }
}

Then the test (I stripped some stuff from my test, so it may not compile directly):

import akka.actor.{ActorRef, ActorSystem}
import akka.testkit.{TestKit, TestProbe}
import module.CommonModule
import org.specs2.mutable.Specification
import org.specs2.specification.Scope
import play.api.inject._
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.test.Helpers._

class SwitchUpdateActorSpec extends Specification {

  "MyActor" should {

    val actorSystem = ActorSystem("test")
    class Actors extends TestKit(actorSystem) with Scope

    val app = new GuiceApplicationBuilder(modules = Seq(new CommonModule))
      .overrides(bind[ActorSystem].toInstance(actorSystem))
      .build()


    "respond with 'ok' upon receiving a message" in new Actors {
      running(app) {
        private val injector: Injector = app.injector
        private val actor: ActorRef = injector.instanceOf(BindingKey(classOf[ActorRef]).qualifiedWith("actor-name"))

        val probe = TestProbe()
        actor.tell("hi there!", probe.ref)

        probe.expectMsg("ok")
      }
    }
  }    
}

So what I did was:

  • create a fresh ActorSystem
  • wrap that actorSystem in Akka's TestKit (libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % "2.4.1")
  • use the GuiceApplicationBuilder to apply the override
  • and then use the app.injector directly to get access to my guice configured actor

It becomes quite obviously what happens, when you look into the implementation of bindActor that you are using in your MyModule.configure() method:

  def bindActor[T <: Actor: ClassTag](name: String, props: Props => Props = identity): Unit = {
    accessBinder.bind(classOf[ActorRef])
      .annotatedWith(Names.named(name))
      .toProvider(Providers.guicify(Akka.providerOf[T](name, props)))
      .asEagerSingleton()
  }
like image 78
mana Avatar answered Sep 18 '22 03:09

mana