自定义ApplicationLoader如何启动Dependency Injecting Actors并进行测试?

时间:2017-05-12 17:08:26

标签: scala dependency-injection akka application-loader

In" Dependency Injecting Actors"它展示了如何将参数注入到子actor的构造函数中。父actor使用injectChild允许传递给子(在子创建时)只有非注入参数,然后让Guice注入其余的参数。为此,它扩展了InjectedActorSupport,并在构造函数中注入了子工厂:

class MyParent @Inject() (childFactory: MyChild.Factory,
                           @Assisted something: Something,
                           @Assisted somethingElse: SomethingElse) extends Actor with InjectedActorSupport
[..]
    val child: ActorRef = injectedChild(childFactory(something, somethingElse), childName)

但是启动父节点而不是演员而是自定义ApplicationLoader的类呢? 我如何从那里开始父演员?文档中没有提到这一点。

我尝试为装载机做同样的事情,就像我为父母做的那样:

class MyLoader @Inject() (parentFactory: MyParent.Factory) extends ApplicationLoader with Actor with InjectedActorSupport {
[..]
val parent = injectedChild(parentFactory(something, somethingElse), parentName)
这是正确的吗?我该怎么测试呢?

class MyModule extends AbstractModule with AkkaGuiceSupport {
  def configure = {
    bindActor[MyParent](parentName)
    bindActor[MyLoader](loaderName)
    bindActorFactory[MyChild, MyChild.Factory]
    bindActorFactory[MyParent, MyParent.Factory]
  }
}

所以:

  1. 我如何从MyLoader启动父级,同时让Guice依赖注入所需的内容?
  2. 如何测试MyLoader? 到目前为止这是我的测试,但现在我需要将注入的东西传递给MyLoader并且我不知道如何(注意*** ??? ****代替我不知道的论点在哪里找到):

    class MyLoaderSpec(_system:ActorSystem,implicit val ec:ExecutionContext)使用带有Matchers的BeforeAndAfterAll扩展带有WordSpecLike的TestKit(_system){   val loader = new SimstimLoader( ???

    覆盖def beforeAll():Unit = {     loader.load(ApplicationLoader.createContext(new Environment(new File("。"),ApplicationLoader.getClass.getClassLoader,Mode.Test)))   }

  3. 提前感谢一百万人!

1 个答案:

答案 0 :(得分:1)

以下是我解决这个问题的方法。

<强> - &GT;如何启动需要依赖注入的父actor。 首先,如果你像我一样需要依赖注入一个你不知道如何通过的实例以及来自哪里,那么手动启动这样一个actor是不可能的。解决方案是让Guice自动启动actor。这是怎么回事。 首先,为Guice创建活页夹模块:

class MyModule extends AbstractModule with AkkaGuiceSupport{

  override def configure(): Unit = {
    bindActor[Root](Root.NAME)
    bind(classOf[StartupActors]).asEagerSingleton()
  }
}

然后,通过在conf / application.conf中添加以下内容,告诉Play你的活页夹模块所在的位置:

play.modules={
  enabled += "my.path.to.MyModule"
}

StartupActors只是一个我用来记录依赖注入的actor的自动启动实际发生的类。我记录事件,以便我可以确定它何时以及是否发生:

class StartupActors @Inject() (@Named(Root.NAME) root: ActorRef) {
  play.api.Logger.info(s"Initialised $root")
}

我的案例中的Root actor负责解析自定义配置。由于我的父actor需要解析产生的变量,并且在测试期间我需要模拟这样生成的变量,我将解析委托给除了父actor之外的actor,即Root actor:

object Root {
  final val NAME = "THERoot"
  case class ParseConfiguration()
}

class Root @Inject()(configuration: Configuration, projectDAO: ProjectDAO) extends Actor {
  val resultingVar: Something = myConfigParsing()

  override def preStart(): Unit = {
    context.actorOf(Props(new MyParent(resultingVar: Something, somethingElse: SomethingElse, projectDAO: ProjectDAO)))
  }

  override def receive: Receive = {
    case ParseConfiguration => sender ! myConfigParsing()
    case _ => logger.error("Root actor received an unsupported message")
  }
}

ParseConfiguration消息唯一用于测试目的。通常,配置解析会因为initialVar属性的初始化而发生。

这样,MyParent不需要注入任何东西。只会注入StartupActors和Root。 MyParent将简单地从Root获取projectDAO并将其传递给其所有子项。

class MyParent(something: Something, somethingElse: SomethingElse, projectDAO: ProjectDAO) extends Actor { ... }

最后,为了完成,我在这里报告我是如何编写测试的,因为我也遇到了麻烦在线找到足够的信息。

import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import com.typesafe.config.ConfigFactory
import org.mockito.Mockito.mock
import org.scalatest.{BeforeAndAfterAll, WordSpecLike}
import org.specs2.matcher.MustMatchers
import play.api.Configuration
import scala.concurrent.ExecutionContext

class RootSpec(_system: ActorSystem) extends TestKit(_system)
  with WordSpecLike with BeforeAndAfterAll with MustMatchers {

  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global
  val conf: com.typesafe.config.Config = ConfigFactory.load()
  val configuration: Configuration = Configuration(conf)
  val projectDAOMock: ProjectDAO = mock(classOf[ProjectDAO])

  private var mainActor: ActorRef = _
  private var something: Something = Something.empty

  def this() = this(ActorSystem("MySpec"))

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

  override def beforeAll(): Unit = {
    mainActor = system.actorOf(Props(new Root(configuration, projectDAOMock)), Root.NAME)
  }

  "RootSpec: Root Actor" should {
    val probe = TestProbe()

    "successfully parse the configuration file" in {
      probe.send(mainActor, ParseConfiguration)
      something = probe.expectMsgPF() {
        case msg => msg.asInstanceOf[Something]
      }
    }
  }
}

然后我通过方便地提供模拟对象代替配置解析产生的变量来测试MyParent:

import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import org.mockito.Mockito
import org.mockito.Mockito._
import org.scalatest.{BeforeAndAfterAll, WordSpecLike}
import org.specs2.matcher.MustMatchers
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ExecutionContext, Future}

case class AnyProjectAPI(val projectAPI: ProjectAPI) extends AnyVal
class MyParentSpec(_system: ActorSystem, implicit val ec: ExecutionContext) extends TestKit(_system)
  with WordSpecLike with BeforeAndAfterAll with MustMatchers {
  val something = mock(classOf[Something])
  val somethingElse = mock(classOf[somethingElse])
  val projectDAOMock: ProjectDAO = mock(classOf[ProjectDAO])

  val projectTest: ProjectAPI = new ProjectAPI(allMyRandomConstructorArguments),
  val projectsList: List[ProjectAPI] = List(projectTest)
  val expectedCreationId = 1
  private var parent: ActorRef = _

  def this() = this(ActorSystem("MySpec"), scala.concurrent.ExecutionContext.global)

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

  override def beforeAll(): Unit = {
    parent = system.actorOf(Props(new MyParent(something, somethingElse, projectDAOMock)), MyParent.NAME)
  }

  "MyParentTesting: parent's pull request" should {
    when(myProjApi.getAllProjects).thenReturn(Future {projectsList})
    val anyProject: AnyProjectAPI = AnyProjectAPI(org.mockito.Matchers.any[ProjectAPI])
    Mockito.when(projectDAOMock.create(org.mockito.Matchers.any[ProjectAPI]))
      .thenReturn(Future {expectedCreationId}: Future[Int])
    val probe = TestProbe()
    val probe1 = TestProbe()

    "be successfully satisfied by all children when multiple senders are waiting for an answer" in {
      probe.send(parent, UpdateProjects)
      probe1.send(parent, UpdateProjects)
      allChildren.foreach(child =>
        probe.expectMsg(expectedCreationId))
      allChildren.foreach(child =>
        probe1.expectMsg(expectedCreationId))
    }
  }
}