首页 文章

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

提问于
浏览
1

在“Dependency Injecting Actors”中,它在构造函数中注入了's shown how to inject a parameter into the constructor of a child actor. The parent actor uses injectedChild to be allowed to pass to the child (at child creation time) only the non-injected parameter and then let Guice inject the rest. To do this, it extends InjectedActorSupport and gets the child'工厂:

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]
  }
}

所以:

  • 如何在让Guice依赖 - 注入所需内容的同时从MyLoader启动父级?

  • 如何测试MyLoader?到目前为止,这是我的测试,但现在我需要将注入的东西传递给MyLoader,我不知道如何(注意*** ??? ****代替我不知道在哪里的参数找):

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

override def beforeAll():Unit = {loader.load(ApplicationLoader.createContext(new Environment(new File(“ . ”),ApplicationLoader.getClass.getClassLoader,Mode.Test)))}

提前一百万!

1 回答

  • 1

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

    --> How to start a parent actor who needs dependency-injection. 首先,如果你像我一样需要依赖注入一个你不知道如何通过的实例以及来自哪里,那么手动启动这样一个actor是不可能的 . 解决方案是让Guice自动启动actor . 这是怎么回事 . 首先,为Guice创建 Binders 模块:

    class MyModule extends AbstractModule with AkkaGuiceSupport{
    
      override def configure(): Unit = {
        bindActor[Root](Root.NAME)
        bind(classOf[StartupActors]).asEagerSingleton()
      }
    }
    

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

    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))
        }
      }
    }
    

相关问题