Wednesday, 4 September 2013

Failing a scalatest when akka actor throws exception outside of the test thread

Failing a scalatest when akka actor throws exception outside of the test
thread

I've had a situation come up and bite me a few times where I'm testing an
Actor and the Actor throws an exception unexpectedly (due to a bug), but
the test still passes. Now most of the time the exception in the Actor
means that whatever the test is verifying won't come out properly so it
the test fails, but in rare cases that's not true. The exception occurs in
a different thread than the test runner so the test runner knows nothing
about it.
One example is when I'm using a mock to verify some dependency gets
called, and due to a mistake in the Actor code I call an unexpected method
in the mock. That causes the mock to throw an exception which blows up the
actor but not the test. Sometimes this can even cause downstream tests to
fail mysteriously because of how the Actor blew up. For example:
// using scala 2.10, akka 2.1.1, scalatest 1.9.1, easymock 3.1
// (FunSpec and TestKit)
class SomeAPI {
def foo(x: String) = println(x)
def bar(y: String) = println(y)
}
class SomeActor(someApi: SomeAPI) extends Actor {
def receive = {
case x:String =>
someApi.foo(x)
someApi.bar(x)
}
}
describe("problem example") {
it("calls foo only when it receives a message") {
val mockAPI = mock[SomeAPI]
val ref = TestActorRef(new SomeActor(mockAPI))
expecting {
mockAPI.foo("Hi").once()
}
whenExecuting(mockAPI) {
ref.tell("Hi", testActor)
}
}
it("ok actor") {
val ref = TestActorRef(new Actor {
def receive = {
case "Hi" => sender ! "Hello"
case "Bye" => throw new IllegalStateException("Boom!")
}
})
ref.tell("Hi", testActor)
expectMsg("Hello")
}
}
"problemExample" passes, but then downstream "ok actor" fails for some
reason I don't really understand... with this exception:
cannot reserve actor name '$$b': already terminated
java.lang.IllegalStateException: cannot reserve actor name '$$b': already
terminated
at
akka.actor.dungeon.ChildrenContainer$TerminatedChildrenContainer$.reserve(ChildrenContainer.scala:86)
at akka.actor.dungeon.Children$class.reserveChild(Children.scala:78)
at akka.actor.ActorCell.reserveChild(ActorCell.scala:306)
at akka.testkit.TestActorRef.<init>(TestActorRef.scala:29)
So, I can see ways of catching this sort of thing by examining the logger
output in afterEach handlers. Definitely doable, although a little
complicated in cases where I actually expect an exception and that's what
I'm trying to test. But is there any more direct way of handling this and
making the test fail?

No comments:

Post a Comment