Skip to main content

Testing Actors in Akka

Posted by manning_pubs on September 28, 2012 at 9:11 AM PDT



by Raymond Roestenburg and Rob Bakker, authors of Akka in Action

Save 40% on Akka in Action and other selected books.

ScalaTest is a xUnit style testing framework. An actor is an object that can be reached through an address, processes messages from a mailbox and sends messages to other actors using the same type of addresses. An actor encapsulates state; it does not share this state with anyone. An actor has behavior, it does something specific with the messages it receives. This article, based on a portion of chapter 2 from Akka in Action, shows how to test actors and send one-way messages. Click here for 40% savings on Akka in Action and other related titles.


Testing actors is more difficult than testing normal objects for a couple of reasons:

  • Sending messages is asynchronous, so it is difficult to know when to assert expected values in the unit test.
  • Actors are meant to be run in parallel on several threads. Multi-threaded tests are more difficult than single-threaded tests and require concurrency primitives like locks, latches, and barriers to synchronize results from various actors. Exactly the kind of thing we wanted to get a bit further away from.
  • Incorrect usage of just one barrier can block a unit test which in turn halts the execution of a full test suite.
  • An actor hides its internal state and does not allow access to this state. Access should only be possible through the ActorRef. Calling a method on an actor and checking its state is prevented on purpose, which is something you would like to be able to do when unit testing.
  • If you want to do an integration test of a couple of actors, you would need to eavesdrop in between the actors to assert if the messages have the expected values. It’s not immediately clear how this can be done.

Luckily, Akka provides the akka-testkit module. This module contains a number of testing tools that makes testing actors a lot easier. The testkit module makes a couple of different types of tests possible:

  • Single-threaded unit testing—An actor instance is normally not accessible directly. The testkit provides a TestActorRef, which allows access to the underlying actor instance. This makes it possible to just test the actor instance directly by calling the methods that you have defined or even call the receive function in a single threaded environment, just as you are used to when testing normal objects.
  • Multi-threaded unit testing—The testkit module provides the TestKit and TestProbe classes, which make it possible to receive replies from actors, inspect messages, and set timing bounds for particular messages to arrive.

The TestKit has methods to assert expected messages. Actors are run using a normal dispatcher in a multi-threaded environment.

Akka also provides tools for testing multiple JVMs, which comes in handy when you want to test remote actor systems.

The TestActorRef extends the LocalActorRef class and sets the dispatcher to a CallingThreadDispatcher that is built for testing only. The CallingThreadDispatcher invokes the actors on the calling thread instead of on a separate thread.

Depending on your preference, you might use one of the styles more often. The option that is of course closest to actually running your code in production is the multi-threaded style testing with the TestKit class. We will focus more on the multi-threaded approach to testing, since this can show problems with the code that will not be apparent in a single-threaded environment. (You probably will not be surprised that we also prefer a classical unit testing approach over a mocking approach).

Before we start, we will have to do a little preparation so that we don't repeat ourselves unnecessarily. Once an actor system is created, it is started and continues to run until it is stopped. Let’s build a small trait that we can use for all the tests and make sure that the system under test is automatically stopped when the unit test ends.

Listing 1 Stopping the system after all tests are done

import org.scalatest.{ Suite, BeforeAndAfterAll }
import akka.testkit.TestKit

trait StopSystemAfterAll extends BeforeAndAfterAll {                 #1
  this: TestKit with Suite  =>                                          #2
  override protected def afterAll() {
    super.afterAll()
    system.shutdown()                                                #3
  }
}


#1 Extends from the BeforeAndAfterAll ScalaTest trait.

#2 Can only be used if it is mixed in with a test that uses the TestKit.

#3 Shuts down the system after all tests have executed.

We will mixin this trait when we write our tests, so that the system is automatically shut down after all tests are executed. The TestKit exposes a value, which can be accessed in the test to create actors system and everything else you would like to do with the system.

We are going to use the testkit module to test some common scenarios when working with actors, both in a single-threaded and in a multi-threaded environment. There are only a few different ways for the actors to interact with each other. We will explore one-way messages in this article.

One-way messages

Sending one-way messages is preferred over two-way request response style messaging, which is also possible. This is because one-way messaging is more applicable in an event-driven style. It is very easy to block and wait when handling two-way messages, which is what most are used to when doing a request to a server of some kind, which is a habit that would be better to get rid of.

Sending with tell is done one way and in fire-and-forget style. We don't know when the message arrives at the actor or if it even arrives, so, how do we test this? What we would like to do is send a message to an actor, and, after sending the message, check that the actor has done the work it should have done. An actor that responds to messages should do something with the message and take some kind of action, like send a message to another actor, or store some internal state, or interact with another object, or with I/O for instance in some kind of way. If the actor’s behavior is completely invisible from the outside, we can only check if it handled the message without any errors, and we could try to look into the state of the actor with the TestActorRef. There are a couple of variations that we will look at:

  1. An actor's behavior is not directly observable from the outside; it might be an intermediate step that the actor takes to create some internal state. We want to test that the actor at least handled the message and did not throw any exception. We want to be sure that the actor has finished. We want to test the internal state change. We will call this type of actor a SilentActor.
  2. An actor sends a message to another actor (or possibly many actors) after it is done processing the received message. We will treat the actor as a black box and inspect the message that is sent out in response to the message it received. We will call this type of actor a SendingActor.
  3. An actor receives a message and interacts with a normal object in some kind of way. After we send a message to the actor, we would like to assert if the object was effected. We will call this type of actor a SideEffectingActor.

We will write a test for each type of actor in the above list. Let's start with the SilentActor. Since it's our first test, let's go briefly through the use of ScalaTest:

Listing 2 First test for the silent actor type

class SilentActor01Test extends TestKit(ActorSystem(“testsystem”)) //   #1
  with WordSpec //                                                      #2
  with MustMatchers //                                                  #3
  with StopSystemAfterAll { //                                          #4

  “A Silent Actor” must { //
    “change state when it receives a message, single threaded” in { //  #5
      //Write the test, first fail
      fail(“not implemented yet”)
    }
    “change state when it receives a message, multi-threaded” in {
      //Write the test, first fail
      fail(“not implemented yet”)
    }
  }
}


#1 Extends from TestKit and provide an actor system for testing.

#2 WordSpec provides an easy to read DSL for testing.

#3 MustMatchers provides easy to read assertions.

#4 Make sure the system is stopped.

#5 MustMatchers provides easy to read assertions.

The above code is the basic skeleton that we need to start running a test for the silent actor. We're using the WordSpec style of testing since it makes it possible to write the test as a number of textual specifications, which will also be shown when the test is run. In the above code, we have created a specification for the silent actor type with a test that should pass as it says “change internal state when it receives a message.” Right now, it always fails since it is not implemented yet, as expected in Red-Green-Refactor style, where you first make sure the test fails (Red), then implement the code to make it pass (Green), after which you might refactor the code to make it nicer. First, we will test the silent actor in a single-threaded environment. We've included the TestKit already since we are also going to test if everything works well in a multi-threaded environment a bit later, which is not necessary if you only use the TestActorRef. Below, we have defined an Actor that does nothing, and will always fail the tests:

Listing 3 First failing implementation of the silent actor type

class SilentActor extends Actor {
  def receive = {
    case msg ()
  }
}


#1 Swallows any message; does not keep any internal state.

Now let’s first write the test to send the silent actor a message and check that it changes its internal state. The SilentActor02 actor will have to be written for this test to pass, as well as an object called SilentActor02Protocol. This object contains all the messages that SilentActor02 supports, which is a nice way of grouping messages that are related to each other.

Listing 4 Single-threaded test internal state

“change internal state when it receives a message, single” in {
  import SilentActor02Protocol._ //                                   #1
  val silentActor = TestActorRef[SilentActor02] //                    #2
  silentActor ! SilentMessage(“whisper”)
  silentActor.underlyingActor.state must (contain(“whisper”)) //      #3
}


#1 Import the messages.

#2 Gets a TestActorRef for single-threaded test.

#3 Getting the underlying actor and assert the state.

We first create a TestActorRef for the actor, which gives us a single-threaded environment to run the actor in. We then send a SilentMessage message to the actor. The SilentActor02 actor is expected to have an internal state which can be accessed by a state method. The underlyingActor method gives access to the underlying actor instance on which we can call the state method. Finally, we assert with a must matcher that the state contains the data that we sent it. Now let’s write the SilentActor02 actor:

Listing 5 SilentActor02 implementation

object SilentActor02Protocol {                                         #1
  case class SilentMessage(data:String)                                #2
}

class SilentActor02 extends Actor {
  import SilentActor02Protocol._
  var internalState = Vector[String]()

  def receive = {
    case SilentMessage(data) =>
      internalState = internalState :+ data                            #3
  }

  def state = internalState
}


#1 A protocol that keeps related messages together.

#2 The message type that the SilentActor02 can process.

#3 The state is kept in a Vector; every message is added to this Vector.

The actor above keeps state in a Vector, which is an immutable list that has quite good performance for general use. The internalState var is changed when a SilentMessage is received. We extract the data field and create a new Vector that has the data field appended to it. In general, it is good practice to prefer vars in combination with immutable data structures instead of vals in combination with mutable data structures. The state method returns the internalState Vector, which is purely used for testing. Since it is immutable, the test can't accidentally change the list and cause problems when asserting the expected result. It's completely safe to set/update the internalState var, since the Actor is protected from multi-threaded access.

Now let’s look at the multi-threaded version of this test. As you will see, we will have to change the code for the actor a bit as well. Just like in the single-threaded version where we added a state method to make it possible to test the actor, we will have to add some code to make the multi-threaded version testable.

Listing 6 Multi-threaded test of internal state

“change internal state when it receives a message, multi” in {
  import SilentActor03Protocol._ //                                    #1
  val silentActor = system.actorOf(Props[SilentActor03],”s3”) //       #2
  silentActor ! SilentMessage(“whisper1”)
  silentActor ! SilentMessage(“whisper2”)
  silentActor ! GetState(testActor) //                                 #3
  expectMsg(Vector(“whisper1”, “whisper2”)) //                         #4
}


#1 A protocol that keeps related messages together.

#2 The test system is used to create an actor.

#3 A message is added to the protocol to get the state.

#4 Used to check which message has been sent to the testActor.

The multi-threaded test uses the “testsystem” ActorSystem, part of the TestKit, to create a SilentActor03 actor. Since we now cannot get to the actor instance, we'll have to think of something else. For this, a GetState message, which takes an ActorRef, is added. The TestKit has a testActor, which you can use to receive messages that you would like to expect. The SilentActor03 actor will have to send its internal state to the ActorRef that is passed inside the GetState expectMsg. That way, we can call the method, which expects one message to be sent to the testActor and asserts the message; in this case, that it is a Vector with all the data fields in it.

Timeout settings for the expectMsg* methods

Timeout settings for the expectMsg* methods
The TestKit has several versions of the expectMsg and other methods for asserting messages. All of these methods expect a message within a certain amount of time; otherwise, they timeout and throw an exception. The timeout has a default value that can be set in the configuration using the “akka.test.single-expect-default” key.
A dilation factor is used to calculate the actual time that should be used for the timeout (it is normally set to 1, which means the timeout is not dilated). The dilation factor is used since not every machine is the same, and you would like the tests to run correctly both on fast workstations as well on possibly slower continuous integration servers.
Based on where the tests are run, the “akka.test.timefactor” dilation factor in the configuration could be changed accordingly. The max timeout can also be set on the method directly, but it is better to just use the configured values, and change the values across tests in the configuration if necessary.

Let’s write the code for the silent actor that can also process GetState messages:

Listing 7 SilentActor03 implementation

object SilentActor03Protocol {
  case class SilentMessage(data:String)
  case class GetState(receiver:ActorRef)                               #1
}

class SilentActor03 extends Actor {
  import SilentActor03Protocol._
  var internalState = Vector[String]()

  def receive = {
    case SilentMessage(data) =>
      internalState = internalState :+ data
    case GetState(receiver) => receiver ! internalState                #2
}
}


#1 The GetState message is added for testing purposes.

#2 The internal state is sent to the ActorRef in the GetState message.

The internal state is sent back to the ActorRef in the GetState message, which in this case will be the testActor. Since the internal state is an immutable Vector, this is completely safe.

Let’s look at the next type of one-way actor type that you will probably use quite often, the SendingActor. A SendingActor receives a message, processes it and sends off a message to another actor for further processing. In this case, we will use a simplified scenario from the kiosk example of a Game that holds a number of Tickets, where every kiosk takes of a number of tickets and passes the game unto the next kiosk. We will write a test where we expect a Kiosk01 actor to receive a game message, to take one ticket off the game and pass the game with one less ticket to the next kiosk. This is what the test looks like:

Listing 8 Kiosk01 test

“A Sending Actor” must {
  “send a message to an actor when it has finished” in {
    import Kiosk01Protocol._
    val props = Props(new Kiosk01(testActor)) //                       #1
    val sendingActor = system.actorOf(props, “kiosk1”)
    val tickets = Vector(Ticket(1), Ticket(2), Ticket(3))
    val game = Game(“Lakers vs Bulls”,tickets) //                      #2
    sendingActor ! game
    expectMsgPF() {
      case Game(_,tickets) => //                                       #3
      tickets.size must be (game.tickets.size - 1) //                  #4
    }
  }
}


#1 The next Kiosk is passed to the constructor, in the test we pass in a testActor.

#2 A game message is created with three tickets.

#3 The testActor should receive a Game.

#4 The testActor should receive one ticket less.

A game is sent to the Kiosk01 Actor, which should process the Game and take off one Ticket and send it off to the next Kiosk. In the test, we pass in the testActor instead of another Kiosk, which is easily done, since the nextKiosk is just an ActorRef. Since we cannot exactly know which ticket was taken off, we can't use a expectMsg(msg) since we can't formulate an exact match for it. In this case, we use expectMsgPF which takes a partial function just like the receive of the actor. Here, we match the message that was sent to the testActor, which should be a Game with one less ticket. Now, let’s write the Kiosk1 actor and the message protocol:

Listing 9 Kiosk01 implementation

object Kiosk01Protocol {
  case class Ticket(seat:Int) //
  case class Game(name:String, tickets:Seq[Ticket]) //
}

class Kiosk01(nextKiosk:ActorRef) extends Actor {
  import Kiosk01Protocol._
  def receive = {
    case game @ Game(_, tickets) =>
      nextKiosk ! game.copy(tickets = tickets.tail) //
  }
}


#1 The simplified Ticket message.

#2 The Game contains tickets.

#3 An immutable copy is made of the Game message with one less ticket.

We once again create a protocol that keeps all related messages together. The actor matches the message and extracts the Kiosk1 Game tickets out of it (it's not interested in the first field which is the name of the game), and assigns an alias to the message named game. Next it creates an immutable copy using the copy method that every case class has. The copy will only contain the tail of the list of tickets, which is for an empty list if where no tickets are left, or everything except for the first ticket in the list. Once again, we take advantage of the immutable property of case classes. The game is sent along to the nextKiosk.

Let’s look at some variations of the SendingActor type. Here are some common variations on the theme:

  • The actor creates a mutated copy and sends the copy to the next actor, which is the case that we have seen just now. We will call this a MutatingCopyActor.
  • The actor forwards the message it receives; it does not change it at all.
  • We will call this a ForwardingActor.
  • The actor sends only certain messages through that it receives. We will call this a FilteringActor.
  • The actor creates a different type of message from the message that it receives. We will call this a TransformingActor.
  • The actor creates many messages based on one message it receives and sends the new messages one after the other to another actor. We will call this a SequencingActor.

The MutatingCopyActor, ForwardingActor, and TransformingActor can all be tested in the same way. We can pass in a testActor as the next actor to receive messages and use the expectMsg or expectMsgPF to inspect the messages.

The FilteringActor is a bit different. How can we assert that some messages where not passed through? The SequencingActor needs a similar approach. How can we assert that we receive the right amount of messages? The next test will show you how. Let’s write a test for the FilteringActor. The FilteringActor we are going to build should filter out duplicate events. It will keep a list of the last messages that it has received and check this list if the incoming message is a duplicate.

Listing 10 FilteringActor test

“filter out particular messages” in {
  import FilteringActorProtocol._
  val props = Props(new FilteringActor(testActor,5))
  val filter = system.actorOf(props, “filter-1”)
  filter ! Event(1) //                                                 #1
  filter ! Event(2)
  filter ! Event(1)
  filter ! Event(3)
  filter ! Event(1)
  filter ! Event(4)
  filter ! Event(5)
  filter ! Event(5)
  filter ! Event(6)
  val eventIds = receiveWhile() { //                                    #2
    case Event(id) if id <= 5 => id
  }
  eventIds must be (List(1,2,3,4,5)) //                                 #3
  expectMsg(Event(6))
}


#1 Sends a couple of events, including duplicates.

#2 Receives messages until the case statement does not match that any more.

#3 Asserts the duplicates are not in the result.

The test uses a receiveWhile method to collect the messages that the testActor receives until the case statement matches. In the test the Event(6) does not match the pattern in the case statement, which defines that all Events with an id smaller or equal than 5 are going to be matched. The receiveWhile method returns the collected items as they are returned in the partial function as a list, which is not allowed to have any duplicates. Now, let's write the FilteringActor that will fit this specification:

Listing 11 FilteringActor implementation

object FilteringActorProtocol {
  case class Event(id:Long)
}

class FilteringActor(nextActor:ActorRef,
                     bufferSize:Int) extends Actor { //                 #1
  import FilteringActorProtocol._
  var lastMessages = Vector[Event]() //                                 #2
  def receive = {
    case msg:Event =>
      if (!lastMessages.contains(msg)) {
        lastMessages = lastMessages :+ msg
        nextActor ! msg //                                              #3
        if(lastMessages.size > bufferSize) {
          // discard the oldest
          lastMessages = lastMessages.tail //                           #4
        }
      }
  }
}


#1 A max size for the buffer is passed into the constructor.

#2 A vector of last messages is kept.

#3 The event is sent to the next actor if it is not found in the buffer.

#4 The oldest event in the buffer is discarded when the max buffersize is reached.

The above keeps a buffer of the last FilteringActor messages that it received in a Vector and adds every received message to it if it does not already exist in the list. Only if the message has not been recently received will it send the message to the nextActor. The oldest message that was received is discarded when a max bufferSize is reached to prevent that the lastMessages list grow too large and possibly run out of space.

The receiveWhile method can also be used for testing a SequencingActor, where you could assert if the sequence of messages that is caused by a particular event is as expected. Two methods for asserting messages that might come in handy when you need to assert a number of messages are ignoreMsg and expectNoMsg. ignoreMsg takes a partial function just like the expectMsgPF method, only instead of asserting the message it ignores any message that matches the pattern. This can come in handy if you are not interested in many messages, but only want to assert that particular messages have been sent to the testActor. The expectNoMsg asserts that no message has been sent to the testActor for a certain amount of time, which we could have also use in between the sending of duplicate messages in the FilteringActor test. The following test shows an example of using expectNoMsg.

Listing 12 FilteringActor implementation

“filter out particular messages using expectNoMsg” in {
  import FilteringActorProtocol._
  val props = Props(new FilteringActor(testActor,5))
  val filter = system.actorOf(props, “filter-2”)
  filter ! Event(1)
  filter ! Event(2)
  expectMsg(Event(1))
  expectMsg(Event(2))
  filter ! Event(1)
  expectNoMsg
  filter ! Event(3)
  expectMsg(Event(3))
  filter ! Event(1)
  expectNoMsg
  filter ! Event(4)
  filter ! Event(5)
  filter ! Event(5)
  expectMsg(Event(4))
  expectMsg(Event(5))
  expectNoMsg()
}

Since the expectNoMsg has to wait for a timeout to be sure that no message was received, the above test will run a lot slower. As we've seen, the TestKit provides a testActor that can receive messages, which we can assert with expectMsg and other methods. A TestKit has only one testActor and since the TestKit is a class that you need to extend, how would you test an actor that sends messages to more than one actor? The answer is the TestProbe class. The TestProbe class is very much like the TestKit, only you can use this class without having to extend from it. Simply create a TestProbe with TestProbe() and start using it.

So far, we have tested two types of one-way actors, the SilentActor and the SendingActor types. It's time to get the last one down, the SideEffectingActor. We will use the Greeter Actor from a HelloWorld example shown bellow.

Listing 13 Hello World

import akka.actor.{ ActorLogging, Actor, Props, ActorSystem }

object HelloWorld extends App { //                                     #1
  val system = ActorSystem("hello_world") //                           #2
  val greeter = system.actorOf(Props[Greeter],"greeter")
  greeter ! Greeting("World") //
}

case class Greeting(who: String) //                                    #3

class Greeter extends Actor with ActorLogging { //
  def receive = {
    case Greeting(who) => log.info("Hello " + who + "!") //               #4
  }
}


#1 Just a simple Scala app.

#2 Creates a "hello_world" system.

#3 Sends a greeting to the world!

#4 The Greeting message case class

#5 Extends the Actor, and uses an ActorLogging trait to say hello.

#6 The Greeting message case class.

The Greeter is a special case of a SideEffectingActor, the only thing you can see happening is that it writes to the console. Sometimes this is the case; all you can see is that an actor logs some events, and getting to the internals might be very difficult for all kinds of reasons.

Listing 14 Testing HelloWorld

import Greeter01Test._
  class Greeter01Test extends TestKit(testSystem) //
  with WordSpec
  with MustMatchers
  with StopSystemAfterAll {

  “The Greeter” must {
    “say Hello World! when a Greeting(“World”) is sent to it” in {
      val dispatcherId = CallingThreadDispatcher.Id
      val props = Props[Greeter].withDispatcher(dispatcherId) //       #1
      val greeter = system.actorOf(props)
      EventFilter.info(message = “Hello World!”,
                       occurrences = 1).intercept { //                 #2
        greeter ! Greeting(“World”)
      }
    }
  }
}

object Greeter01Test {
  val testSystem = { //                                                #3
    val config = ConfigFactory.parseString(
    “““akka.event-handlers = [“akka.testkit.TestEventListener”]”““
    )
    ActorSystem(“testsystem”, config)
  }
}


#1 Uses the testSystem from the Greeter01Test object.

#2 Single threaded environment Intercept the log messages that were logged.

#3 Creates a system with a configuration that attaches a test event listener.

The Greeter is tested by inspecting the log messages that it writes using the ActorLogging trait. The testkit module provides a TestEventListener that you can configure to handle all events that are logged. In the above code, we create an ActorSystem with a configuration in the Greeter01Test. The ConfigFactory can parse a configuration file from a String; in this case, we only override the event handlers list.

The test is run in a single threaded environment because we want to check that the log event has been recorded by the TestEventListener when the greeter is sent the “World” Greeting. We use an EventFilter object, which can be used to filter log messages. In this case, we filter out the expected message, which should only occur once. The filter is applied when the intercept code block is executed, which is when we send the message.

The above example of testing a SideEffectingActor shows that asserting some interactions can get complex quite quickly. In a lot of situations it is easier to adapt the code a little bit so that it is easier to test. The below example shows an adapted Greeter Actor, which can be configured to send a message to a listener actor whenever a greeting is logged:

Listing 15 Simplifying testing of the Greeting Actor with a listener

class Greeter02(listener:Option[ActorRef] = None) //                   #1
  extends Actor with ActorLogging {
  def receive = {
    case Greeting(who) =>
      val message = "Hello " + who + "!"
      log.info(message)
      listener.foreach(_ ! message) //                                 #2
  }
}


#1 The constructor takes a optional listener, default set to None.

#2 Optionally sending to the listener.

The actor is adapted so Greeter02 that it takes an Option[ActorRef], which is default set to None. After it successfully logs a message, it sends a message to the listener if the Option is not empty. When the actor is used normally without specifying a listener it runs as usual. Below is the updated test for this Greeter02 actor.

Listing 16 Simpler Greeting Actor test

class Greeter02Test extends TestKit(ActorSystem(“testsystem”))
with WordSpec
with MustMatchers
with StopSystemAfterAll {

  “The Greeter” must {
    “say Hello World! when a Greeting(“World”) is sent to it” in {
      val props = Props(new Greeter02(Some(testActor))) //              #1
      val greeter = system.actorOf(props)
      greeter ! Greeting(“World”)
      expectMsg(“Hello World!”) //                                      #2
    }
  }
}


#1 Sets the listener to the testActor.

#2 Asserts the message as usual.

As you can see, the test has been greatly simplified. We simply pass in a Some(testActor) into the Greeter02 constructor and assert the message that is sent to the testActor as usual.

Summary

We learned how to send messages to actors and how to receive and process messages. The tell method is used to send a message to an actor, while the receive partial function is used to handle messages in a pattern-matching style. We took a test driven approach to building a couple common types of actors using ScalaTest and the TestKit module. We found out how SilentActor, SendingActor, and SideEffectingActor types of Actors can be tested, both in a single-threaded as well as in a multi-threaded environment.


Here are some other Manning titles you might be interested in:

Scala in Depth

Scala in Depth
Joshua D. Suereth

Scala in Action

Scala in Action
Nilanjan Raychaudhuri

Play for Scala

Play for Scala
Peter Hilton, Erik Bakker, and Francisco Canedo


AttachmentSize
cover.jpg7.83 KB
image001.jpg1.42 KB
image002.jpg1.55 KB
image003.jpg1.55 KB
AkkainActionCH02.txt34.35 KB