Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test an Akka actor that sends a message to itself, without using Thread.sleep

I have a Scala unit test for an Akka actor. The actor is designed to poll a remote system and update a local cache. Part of the actor's design is that it doesn't attempt to poll while it's still processing or awaiting the result of the last poll, to avoid flooding the remote system when it experiences a slowdown.

I have a test case (shown below) which uses Mockito to simulate a slow network call, and checks that when the actor is told to update, it won't make another network call until the current one is complete. It checks the actor has not made another call by verifying a lack of interactions with the remote service.

I want to eliminate the call to Thread.sleep. I want to test the functionality of the actor without relying on waiting for a hardcoded time, in every test run, which is brittle, and wastes time. The test can poll or block, waiting for a condition, with a timeout. This will be more robust, and will not waste time when the test is passing. I also have the added constraint that I want to keep the state used to prevent extra polling var allowPoll limited in scope, to the internals of the PollingActor.

  1. is there a way force a wait until the actor is finished messaging itself? If there's a way I can wait until then before trying to assert.
  2. is it necessary to send the internal message at all? Couldn't I maintain the internal state with a threadsafe datastructure, such as java.util.concurrent.AtomicBoolean. I have done this and the code appears to work, but I'm not knowledgeable enough about Akka to know if it's discouraged -- a colleague recommended the self message style.
  3. is there better, out-of-the-box functionality with the same semantics? Then I would opt for an integration test instead of a unit test, though I'm not sure if it would solve this problem.

The current actor looks something like this:

class PollingActor(val remoteService: RemoteServiceThingy) extends ActWhenActiveActor {

  private var allowPoll: Boolean = true

  def receive = {
    case PreventFurtherPolling => {
      allowPoll = false
    }
    case AllowFurtherPolling => {
      allowPoll = true
    }
    case UpdateLocalCache => {
      if (allowPoll) {
        self ! PreventFurtherPolling

        remoteService.makeNetworkCall.onComplete {
          result => {
            self ! AllowFurtherPolling
            // process result
          }
        }
      }
    }
  }
}

trait RemoteServiceThingy {
  def makeNetworkCall: Future[String]
}

private case object PreventFurtherPolling
private case object AllowFurtherPolling

case object UpdateLocalCache

And the unit test, in specs2, looks like this:

"when request has finished a new requests can be made" ! {
  val remoteService = mock[RemoteServiceThingy]
  val actor = TestActorRef(new PollingActor(remoteService))

  val slowRequest = new DefaultPromise[String]()

  remoteService.makeNetworkCall returns slowRequest

  actor.receive(UpdateLocalCache)
  actor.receive(UpdateLocalCache)
  slowRequest.complete(Left(new Exception))

  // Although the test calls the actor synchronously, the actor calls *itself* asynchronously, so we must wait.
  Thread.sleep(1000)

  actor.receive(UpdateLocalCache)

  there was two(remoteService).makeNetworkCall
}
like image 325
Grundlefleck Avatar asked Aug 21 '13 17:08

Grundlefleck


1 Answers

The way we have chosen to solve this for now is to inject the equivalent of an observer into the actor (piggybacking on an existing logger which wasn't included in the listing in the question). The actor can then tell the observer when it has transitioned from various states. In the test code we perform an action then wait for the relevant notification from the actor, before continuing and making assertions.

In the test we have something like this:

actor.receive(UpdateLocalCache)

observer.doActionThenWaitForEvent(
   { actor.receive(UpdateLocalCache) }, // run this action
   "IgnoredUpdateLocalCache" // then wait for the actor to emit an event
}

// assert on number of calls to remote service

I don't know if there's a more idiomatic way, this seems like a reasonable suggestion to me.

like image 106
Grundlefleck Avatar answered Oct 08 '22 16:10

Grundlefleck