Как пользовательский ApplicationLoader может запустить субъекты внедрения зависимостей и пройти тестирование?

В разделе "Субъекты внедрения зависимостей" показано, как вставить параметр в конструктор дочернего актора. Родительский субъект использует 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)

Но как насчет класса, который запускает родительский объект и является не актором, а настраиваемым 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]
  }
}

So:

  1. Как мне запустить родительский элемент из MyLoader, позволяя Guice вводить то, что требуется?
  2. Как я могу протестировать MyLoader? До сих пор это был мой тест, но теперь мне нужно передать введенную штуку в MyLoader, и я не знаю, как (обратите внимание на *** ??? **** вместо аргумента, который я не знаю, куда найти):

    class MyLoaderSpec (_system: ActorSystem, неявный val ec: ExecutionContext) расширяет TestKit (_system) с помощью WordSpecLike с помощью BeforeAndAfterAll с помощью Matchers {val loader = new SimstimLoader (???)

    переопределить def beforeAll (): Unit = {loader.load (ApplicationLoader.createContext (new Environment (new File ("."), ApplicationLoader.getClass.getClassLoader, Mode.Test)))}

Заранее миллион!


person iammyr    schedule 12.05.2017    source источник


Ответы (1)


Вот как я решил эту проблему.

-> Как запустить родительский актор, которому требуется внедрение зависимостей. Прежде всего, запуск такого актора вручную невозможен, если вам, как и мне, необходимо внедрить зависимость экземпляра, который у вас нет. знаю, как пройти и откуда. Решение состоит в том, чтобы позволить Guice запускать актера автоматически. Вот как это сделать. Сначала создайте модуль подшивки для Guice:

class MyModule extends AbstractModule with AkkaGuiceSupport{

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

Затем сообщите Play, где находится ваш модуль связывания, добавив в файл conf / application.conf следующее:

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

StartupActors - это просто класс, который я использую для ведения журнала всякий раз, когда действительно происходит автоматический запуск акторов, внедренных зависимостями. Я регистрирую событие, чтобы быть уверенным, когда и произойдет ли оно:

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

Актер Root в моем случае заботится о разборе настраиваемой конфигурации. Поскольку результирующие вары в результате синтаксического анализа требуются моему родительскому субъекту, и во время тестов мне нужно имитировать такие результирующие вары, я делегирую синтаксический анализ другому субъекту, а не родительскому субъекту, то есть корневому субъекту:

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 используется исключительно в целях тестирования. Обычно анализ конфигурации происходит из-за инициализации атрибута resultVar.

Таким образом, MyParent не нужно будет ничего вводить. Будут введены только StartupActors и Root. MyParent просто получит projectDAO от Root и передаст его всем своим потомкам.

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))
    }
  }
}
person iammyr    schedule 21.05.2017