自定义应用程序加载器如何启动依赖注入参与者并进行测试?



在"依赖注入Actor"中,展示了如何将参数注入到子Actor的构造函数中。父参与者使用 injectedChild 只将未注入的参数传递给子参数(在子节点创建时),然后让 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)

但是,启动父级并且不是参与者而是自定义应用程序加载器的类呢? 如何从那里启动父演员?文档中没有提到这一点。

我尝试对加载器执行与对父级相同的操作:

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,我不知道如何(注意 ***???代替我不知道在哪里找到的论点):

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

    覆盖 def 之前全部(): 单位 = { loader.load(ApplicationLoader.createContext(new Environment(new File("."), ApplicationLoader.getClass.getClassLoader, Mode.Test))) }

提前感谢一百万!

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

--> 如何启动需要依赖注入的父角色。首先,如果您像我一样需要依赖注入一个您不知道如何传递以及从哪里传递的实例,那么手动启动这样的 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"
}

StartupActor只是我用来记录的类,每当依赖注入的Actor的自动启动实际发生时。我记录事件,以便确定它何时发生以及是否发生:

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

在我的例子中,根参与者负责解析自定义配置。由于解析的结果变量是我的父 actor 所必需的,并且在测试期间我需要模拟此类生成的 var,因此我将解析委托给父 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 消息仅用于测试目的。通常,由于 resultingVar 属性的初始化,配置解析会发生。

这样,MyParent就不需要注射任何东西。只有StartupActor和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))
}
}
}

最新更新