Как вы обнаруживаете ошибки и исключения без выключения опекуна при использовании TestActorRef в Akka?
При выполнении синхронного тестирования с TestActorRef
(в соответствии с рекомендациями Testing Actor Systems) иногда желательно проверить, не генерируется ли исключение, отдельно от любых других тестов, которые ожидают передачи сообщений и т. д.
Как это можно сделать надежно в модульном тесте?
Этот ответ показывает, как добавить обработчик для исключений с помощью EventFilter
, Этот подход не работает для Throwable
с которые Error
с однако.
Вот пример кода:
import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import akka.event.Logging
import akka.testkit.{EventFilter, ImplicitSender, TestKit, _}
import com.typesafe.config.{Config, ConfigFactory}
import org.scalatest.{FlatSpec, Matchers}
import scala.collection.mutable
object GreetingProtocol {
case class Greet(msg: String)
case object GreetWithNPE
case object GreetWithError
}
class ErrorLogger() {
val errors = mutable.ListBuffer[Logging.Error]()
val filter = EventFilter.custom {
case e: Logging.Error =>
errors += e
false
}
}
class Greeter extends Actor with ActorLogging {
import GreetingProtocol._
def receive = {
case Greet(who) => log.info(s"Hello $who")
case GreetWithError => ???
case GreetWithNPE => throw new NullPointerException("NPE")
}
}
class GreetingSpec extends FlatSpec with Matchers {
import GreetingProtocol._
def testEventListener(theFilter: EventFilter) = Props(new TestEventListener { addFilter(theFilter) })
def akkaConfiguration: Config = ConfigFactory.parseString(
"""
|akka {
| loglevel = DEBUG
| actor.debug {
| receive = on
| lifecycle = on
| event-stream = on
| }
| remote {
| log-sent-messages = on
| log-received-messages = on
| }
|}
|
""".stripMargin)
"a normal greeting" should "cause the test to succeed" in
new TestKit(ActorSystem("test", akkaConfiguration)) with ImplicitSender {
val errorLogger = new ErrorLogger()
system.eventStream.subscribe(system.actorOf(testEventListener(errorLogger.filter), "ErrorListener"), classOf[Logging.Error])
filterEvents(errorLogger.filter)({
val greeter = TestActorRef[Greeter]("Greeter")
greeter ! Greet("Bob")
})
shutdown()
withClue("errors logged") {
errorLogger.errors should have length 0
}
}
"a NullPointerException" should "be caught by the testing framework and not make the test succeed" in
new TestKit(ActorSystem("test", akkaConfiguration)) with ImplicitSender {
val errorLogger = new ErrorLogger()
system.eventStream.subscribe(system.actorOf(testEventListener(errorLogger.filter), "ErrorListener"), classOf[Logging.Error])
filterEvents(errorLogger.filter)({
val greeter = TestActorRef[Greeter]("Greeter")
greeter ! GreetWithNPE
})
shutdown()
withClue("errors logged") {
errorLogger.errors should have length 1
}
}
"an Error" should "be caught by the testing framework and not make the test succeed" in
new TestKit(ActorSystem("test", akkaConfiguration)) with ImplicitSender {
val errorLogger = new ErrorLogger()
system.eventStream.subscribe(system.actorOf(testEventListener(errorLogger.filter), "ErrorListener"), classOf[Logging.Error])
filterEvents(errorLogger.filter)({
val greeter = TestActorRef[Greeter]("Greeter")
greeter ! GreetWithError
})
shutdown()
withClue("errors logged") {
// Fails because the user guardian is shut down and the ErrorListener is gone with it
errorLogger.errors should have length 1
}
}
}
С этим кодом много проблем, но главная из них заключается в том, что третий тест всегда терпит неудачу из-за сбоя опекуна пользователя со следующим сообщением в выходных данных журнала:
[ERROR] [03/02/2015 11:34:51.260] [test-akka.actor.default-dispatcher-2] [LocalActorRefProvider(akka://test)] guardian failed, shutting down system
Что можно сделать с третьим случаем, чтобы проверка прошла успешно (например, ошибка заносится в список ошибок)?
Есть две другие небольшие проблемы:
- Вызов shutdown() необходим, потому что нет очевидного способа дождаться, пока прослушиватель событий обработает событие, а установка произвольного тайм-аута еще сложнее
- Вызов filterEvents() на самом деле не нужен, но, похоже, все примеры его используют, почему?
Версии:
- Scala 2.11.4
- Акка 2.3.7
- Scalatest 2.2.2