Isolating an ActorSystem from Akka

189 views
Skip to first unread message

Derek Wyatt

unread,
May 14, 2012, 1:09:20 PM5/14/12
to scalatest-users
Hi guys,

I've been trying to isolate Akka's ActorSystem in each test and I'm
hitting some issues - Bill helped me get a fair ways into this but
I've left his path a bit, and I probably need to be put straight
again.

My goal is to create a suite that has no Akka-ness in it, and then
have each test mix in the bits of Akka I'd like to use. The reason
for this is that the ActorSystem is constructed with a name, and I
want a different one for every test, and then shut that system down
when the test is over. This makes it easier for my code to be
designed as though the names it chooses for its Actors can never clash
with anything else. i.e. At any level of hierarchy in an ActorSystem,
you can only have one Actor with the name "Bob". The best way to
ensure you never get a clash is to have multiple ActorSystems.

So, I've made this class:

class AkkaMixin(name: String) extends TestKit(ActorSystem(name))
with ImplicitSender
with SyncStopper
with DelayedInit {
def delayedInit(f: => Unit): Unit = {
try {
f
} finally {
system.shutdown()
}
}
}

And I use it like this:

class TestSpec extends WordSpec with MustMatchers {
"TestSpec" should {
"do the right thing" in new AkkaMixin("Test1") {
// use the facilities of the AkkaMixin
}
}

This causes problems with Akka that I haven't figured out yet. But
I'm wondering if there's simply a better way to do this? I'm not
happy with using DelayedInit and believe it may be the source of my
problems.

Does anyone know of a way to get the same feature (a new TestKit with
a specific ActorSystem, with a unique name, per test and an "after"
that works with that test)? I can't use withFixture(NoArgTest)
because that's on the suite, which doesn't have access to the
ActorSystem on the test.

As well, I'd like to avoid the loan pattern if I can since mixing
things in keeps that natural feel - e.g. my test _is_ an
ImplicitSender, not _has_ an ImplicitSender.

I apologize if there's not enough context to this, so if it's not
clear, please ask for more detail. I just didn't want to overload
people with unwanted background.

Cheers,
Derek

Derek Wyatt

unread,
May 14, 2012, 4:27:43 PM5/14/12
to scalatest-users
I may have my own answer here. I'm not 100% satisfied with it, but it's pretty decent for me at the moment.

import akka.actor.ActorSystem
import akka.testkit.{TestKit, ImplicitSender}
import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers

class AkkaMixin(name: String) extends TestKit(ActorSystem(name)) with ImplicitSender {
def shutdown(): Unit = system.shutdown()
}

class AkkaSpec extends WordSpec with MustMatchers {
class AkkaSpecable(s: String) {
def inAkka(withAkka: => AkkaMixin) {
s in {
withAkka
withAkka.shutdown()
}
}
}
implicit def string2AkkaSpecable(s: String) = new AkkaSpecable(s)
}


Which lets me use it like this:

"TestSpec" should {
"do something correct" inAkka new Akka("Test1") {
// test stuff here
}
}

Now I should be ready for some parallelism, when the time comes.

I'd still be very happy to learn how wrong I am, and how I should have done it properly.
signature.asc

Bill Venners

unread,
May 15, 2012, 11:46:08 AM5/15/12
to scalate...@googlegroups.com
Hi Derek,

I think one simplification might be to just have a factory method that
produces an AkkaMixin. You could get that by making AkkaMixin a case
class. I might also rename it to ActorSys:

case class ActorSys(name: String) extends TestKit(ActorSystem(name))
with ImplicitSender {
def shutdown(): Unit = system.shutdown()
}

Then your tests would look like:

"TestSpec" should {
"do something correct" in ActorSys("Test1") {
// test stuff here
}
}

(I think. I haven't tried it.) One thing that bugs me about that is
that you have to name your tests twice. One way around that is to come
up with an automated name for the actor system. I'm not sure how much
harder that would make it to figure out errors. If it doesn't really
affect understandability of errors, you could perhaps do this:

case class ActorSys extends TestKit(ActorSystem(generateNextName()))
with ImplicitSender {
def shutdown(): Unit = system.shutdown()
}

Then your tests would look like:

"TestSpec" should {
"do something correct" in ActorSys {
// test stuff here
}
}

The other idea I had is to pass the actual test name in as the actor
system name. But that's more boilerplatey. It would look like this:

case class ActorSys(name: String) extends TestKit(ActorSystem(name))
with ImplicitSender {
def shutdown(): Unit = system.shutdown()
}

import org.scalatest._
import matchers.MustMatchers

class TestSpec extends fixture.WordSpec with MustMatchers {

// Use a fixture.WordSpec and pass the test name into the test
type FixtureParam = String
override def withFixture(test: OneArgTest) {
// To make it stackable, let withFixture(NoArgTest) invoke the function
// If you don't want to make it stackable, then just say test(test.name)
withFixture.toNoArgTest(test.name)
}

"TestSpec" should {
"do something correct" in { testName =>
new ActorSys(testName) {
// test stuff here
}
}
}

As I said, more boilerplatey. The best option might be to just name
each test twice. I'll keep thinking. Let me know if you try some of
these and they work or don't. (I'm not the best compiler.)

Bill
--
Bill Venners
Artima, Inc.
http://www.artima.com

Derek Wyatt

unread,
May 15, 2012, 1:39:17 PM5/15/12
to scalatest-users
Hmm...

On May 15, 11:46 am, Bill Venners <b...@artima.com> wrote:
> Hi Derek,
>
> I think one simplification might be to just have a factory method that
> produces an AkkaMixin. You could get that by making AkkaMixin a case
> class

Due to the inheritance issue (i.e. I'd like to derive this from time
to time) case classes might not be problematic.

As for the factory method, that would be nice, but I don't know how to
get the code block to head in there. For example:

new ActorSys(name) { ... } -- versus -- ActorSys(name) { ... }

The error I get from the factory-method version is: too many arguments
for method apply. That seems reasonable to me, and I don't know how
to pass the code block further through to the eventual "new".

def apply(name: String)(???) = new ActorSys(name)

What is ??? such that it's a code block that can be used to complete
the construction of the ActorSys and still be coded as though it were
part of ActorSys to begin with, and thus get access to its internals
without throwing a type error?

> I might also rename it to ActorSys:
>
> case class ActorSys(name: String) extends TestKit(ActorSystem(name))
> with ImplicitSender {
>  def shutdown(): Unit = system.shutdown()
>
> }
>
> Then your tests would look like:
>
> "TestSpec" should {
>  "do something correct" in ActorSys("Test1") {
>    // test stuff here
>  }
>
> }
>
> (I think. I haven't tried it.) One thing that bugs me about that is
> that you have to name your tests twice. One way around that is to come
> up with an automated name for the actor system. I'm not sure how much
> harder that would make it to figure out errors. If it doesn't really
> affect understandability of errors, you could perhaps do this:
>
> case class ActorSys extends TestKit(ActorSystem(generateNextName()))
> with ImplicitSender {
>  def shutdown(): Unit = system.shutdown()
>
> }
>
> Then your tests would look like:
>
> "TestSpec" should {
>  "do something correct" in ActorSys {
>    // test stuff here
>  }
>
> }

That I can go for. I would probably do it like this:

class ActorSys(name: String) extends TestKit... {
def this() = this("TestSystem-
%d".format(scala.util.Random.nextString(5)))
...
}

Which reduces things to:

"TestSpec" should {
"do something correct" in new ActorSys {
// test stuff here
}
}

Pretty close to what might be ideal (i.e. minus the 'new').

The rest is, as you said, pretty boilerplatey and lacks a fair amount
of sex appeal, and what are testing nerds all about, if not sex
appeal?

Thanks Bill. I'm much happier with it now.

Cheers,
Derek

Bill Venners

unread,
May 16, 2012, 12:32:48 AM5/16/12
to scalate...@googlegroups.com
Hi Derek,

You're right. I'd forgotten you needed the inheritance. So I think the
best is "in new ActorSys."

Bill
> --
> You received this message because you are subscribed to the Google
> Groups "scalatest-users" group.
> To post to this group, send email to scalate...@googlegroups.com
> To unsubscribe from this group, send email to
> scalatest-use...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/scalatest-users?hl=en
> ScalaTest itself, and documentation, is available here:
> http://www.artima.com/scalatest
Reply all
Reply to author
Forward
0 new messages