Testing actors using dependency injection in play framework 2.4.x
Asked Answered
H

2

6

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"
Haerle answered 19/9, 2015 at 15:20 Comment(0)
M
4

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()
  }
Mingle answered 10/12, 2015 at 14:14 Comment(0)
T
2

Im writing actor unit tests as such

static ActorSystem system;
static Configuration configuration;
static MyActor myActor;

@BeforeClass
public static void setup() {
    Map<String, Object> stringConf = new HashMap<>();
    configuration = new Configuration(stringConf);

    system = ActorSystem.apply();
    final Props props = Props.create(MyActor.class, configuration);
    final TestActorRef<MyActor> ref = TestActorRef.create(system, props, "myActor");
    myActor = ref.underlyingActor();
}

@AfterClass
public static void teardown() {
    JavaTestKit.shutdownActorSystem(system);
    system = null;
}

then you can call methods in your actor as if it were a regular java class. According to the play framework https://www.playframework.com/documentation/2.5.x/JavaFunctionalTest

It’s generally best practice to inject members only in functional tests and to manually create instances in unit tests.

so thats what im doing here. You will need a dependency on

"com.typesafe.akka" % "akka-testkit_2.11" % "2.4.12" % "test"

for this to work. Hope this helps.

Tamas answered 2/11, 2016 at 17:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.