Pregunta

See unit test below. An Actor class defined inside and outside of the unit test itself appear to behave differently when being instantiated via via system.actorOf but only when using the Props style recommended in the Akka documentation (page 67, Section 3.1 Props of Akka Scala Documentation, Release 2.2.3).

I declare two simple Greeter Actors, one inside and one outside the spec, and then get ActorRefs for each one in two different ways -- once using the deprecated Props style and once using the recommended Props style. Only line [4] throws the IllegalArgumentException.

Any ideas what might be going on? Thanks for any help!

(Note: same issue arises using FunSpec so WordSpec, MustMatchers don't seem to be a factor)

import org.junit.runner.RunWith
import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers
import akka.actor.ActorSystem
import akka.actor.Props
import org.scalatest.junit.JUnitRunner
import akka.actor.Actor

class MyGreeter extends Actor {
  def receive = {
    case _ => println("greetings!")
  }
}

@RunWith(classOf[JUnitRunner])
class HelloAkkaTest extends WordSpec with MustMatchers {

  class MyOtherGreeter extends Actor {
    def receive = {
      case _ => println("greetings!")
    }
  }

  "Greeter" must {
    "greet" in {
      val system = ActorSystem()
      system.actorOf(Props[MyGreeter], "greeter")       // [1] Works
      system.actorOf(Props(new Greeter))            // [2] Works
      system.actorOf(Props(new MyOtherGreeter))     // [3] Works
      system.actorOf(Props[MyOtherGreeter], "other")    // [4] Fails!
    }
  }
}

I am using scalatest 2.10-2.0-RC3 with scala 2.10.2 and akka 2.10-2.3.0-RC2

¿Fue útil?

Solución

What you face here is an interesting detail of scala and nested classes. When you have a construct like

class Foo {
  class Bar
}

then the inner class Bar will be bound to a specific instance of Foo, meaning you can't instantiate it outside of this instance.

scala> new Foo
res5: Foo = Foo@5b2cef50

scala> new res5.Bar
res6: res5.Bar = Foo$Bar@64f8b658

scala> new Foo#Bar
  <console>:12: error: Foo is not a legal prefix for a constructor
          new Foo#Bar
                  ^

The Props.apply[A <: Actor] implicitly retrieves an instance of ClassTag[A] an then tries to call the constructor through reflection. So let's have a look at the constructors of Foo#Bar:

scala> classOf[Foo#Bar].getConstructors
res9: Array[java.lang.reflect.Constructor[_]] = Array(public Foo$Bar(Foo))

As you can see, it expects an instance of Foo as parameter. So how do we fix this?

My suggestion would be to just take the class outside of the test class. There are 2 possibilities, either put it directly under the package, as your other class, or put in in the companion of the test class.

Another way to fix this would be to pass a reference to the test class to the Props:

system.actorOf(Props(classOf[MyOtherGreeter], this), "other")
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top