Unit testing in Scala

This post is copied over from my internal blog inside IBM, just for posterity after I’ve left. It was first published in March 2011.

In my Baby Steps in Scala blog post I introduced the group sync problem I used to experiment with Scala as a language. After spending a bit more time learning the language features and syntax I switched focus to start looking at the ecosystem around it, including tooling and frameworks.

In my early experiments I simply used the Eclipse Scala plugin with straightforward project structures. The plugin is pretty basic and prone to crashes. The good news however is that Martin Odersky himself has been working on improving it.

As a strong believer in test-driven development I wanted to explore the ways that Scala code can be tested. I’d already managed to write Junit tests for the group sync example that called the Scala code, but I wanted to look more at specific frameworks for Scala testing. I also wanted to understand how Scala code can be integrated into a build environment.

I’ll cover the testing aspect in this post, and build in another.

A quick Google search showed there are three common testing frameworks around Scala:

The interesting thing is that the focus of all of them is not just “how can I test Scala code” but more on how the capabilities of the Scala language itself can be harnessed to produce tests which are much more natural and easy to define and understand. This is primarily done through Scala’s excellent support for DSLs.

All three frameworks are also more than just Unit test libraries and execution environments. They advocate specification and behaviour driven testing.

I didn’t spend much time investigating each, but based on material I’d read I chose Specs to play around with. Specs itself can make use of ScalaCheck anyway. Specs encourages up-front specification of test cases or behaviour in a very human readable form. For instance, here is the initial specification I wrote for my group sync service:

package com.adrianspender.groupsync.scala.test

import org.specs._

class GroupSyncSpecification extends Specification {

  "group sync service" should {
    "remove any cached groups if the user is no longer in any groups" in {}

    "not change the cache if the groups for the user have not changed" in {}

    "add groups to the cache if the user has been added to new groups " +
      "so that the cache matches the groups" in {}

    "remove groups from the cache if the user has been removed from groups " +
      "so that the cache matches the groups" in {}

    "update the cache when the user has been added to new groups and " +
      "removed from old ones so that the cache matches the groups" in {}
  }
}

As you can see, the specifications are written in natural language that could easily be taken from the acceptance criteria of a story I’m working on. In true TDD/BDD fashion this can be written before any coding takes place. It can also be executed to produce the following output (in this case it is being run through sbt, but I’ll cover that in the entry on building)


(click the images to view full size)

The suite contains one test, known as a group (“group sync service” should) which then defines a set of examples such as “remove any cached groups if the user is no longer in any groups”.

We then expand the examples by specifying expectations, which is really the meat of our test and what would go inside a Junit test method. To take the first example, we need to test that if the user is not in any groups, then the cache should be emptied for them. You can look back at the Baby Steps in Scala post for the actual code we are testing.

At this point, if I was implementing this test in Junit, I’d start thinking about the collaborations that the class I am testing has, and how I can provide mock implementations of them with the behaviour I need. In this case I need:

  • A mock AuthoritativeSource which will return an empty set for the given user
  • A mock PersistentCache on which we need to assert that the removeAllGroups() method gets called

In Java I use the EasyMock library to provide a way to define mock objects with the behaviour I want, and to validate the runtime behaviour is as expected. Specs provides support for using EasyMock (and Mockito) within Specs and all you need to do is change your Specification to add the EasyMock trait:

package com.adrianspender.groupsync.scala.test

import org.specs._
import org.specs.mock.EasyMock
import org.easymock.EasyMock._

class GroupSyncSpecification extends Specification with EasyMock {
  ...
}

This then provides us with a Scala-friendly abstraction over the EasyMock library. We can now define our mock objects, set their expected behaviour, replay them and verify them (if you are unfamiliar with EasyMock, take a look at the tutorial here) With this in place, I am now ready to implement the expectations of the specification:

"remove any cached groups if the user is no longer in any groups" in {
      val authoritativeSource = strictMock[AuthoritativeSource]
      val persistentCache = strictMock[PersistentCache]

      val groups = new java.util.HashSet[String]()
      val personId = "1234"

      authoritativeSource.getGroups(personId) returns groups
      persistentCache.removeAllGroups(personId)

      replay(authoritativeSource, persistentCache)

      val groupSyncService = new GroupSyncServiceImpl(authoritativeSource, persistentCache)
      groupSyncService.syncGroups(personId)

      verify(authoritativeSource, persistentCache)
    }

Stepping through the code we are doing the following:

  1. Creating our mock objects for AuthoritativeSource and PersistentCache
  2. Creating an empty set for AuthoritativeSource to return and defining the userid we will use
  3. Setting the behaviour of our mocks:
    • We expect the getGroups() method of AuthoritativeSource to be called with personId, and we want it to return the empty group set.
    • We then expect the removeAllGroups(personId) method to be called on PersistentCache
  4. We replay both mock objects, so that EasyMock understands what should happen
  5. We create an instance of our GroupSyncServiceImpl – the class we are testing. The collaborators are injected through constructor injection
  6. We call groupSyncService.syncGroups(personId)
  7. We ask EasyMock to verify that the behaviour we expected actually happened.

And that is one part of our specification test implemented. We can then run it again:

You can see that the test we implemented is passing. We could break the test, for instance by doing this:

      val groups = new java.util.HashSet[String]()
      groups.add("group1")

So that now the group set returned from the AuthoritativeSource is not empty. Running this produces:

Here we expected one invocation of removeAllGroups() but there were none.

Let’s fix that and implement the rest of the examples. We then have a passing set of specification tests:

Finally, we have a working set of tests, but for integration into an existing Build/Continuous Integration environment, or even within Eclipse we may want to be able to execute them as JUnit tests. We can do this simply by changing the trait we use to the SpecificationWithJunit trait and annotating the class as such:

package com.adrianspender.groupsync.scala.test

import org.specs._
import org.specs.mock.EasyMock
import org.easymock.EasyMock._
import org.specs.runner.JUnitSuiteRunner
import org.junit.runner.RunWith

@RunWith(classOf[JUnitSuiteRunner])
class GroupSyncSpecification extends SpecificationWithJUnit with EasyMock {
  ...
}

Then we can simply run the whole thing as a Junit test in Eclipse:

So, Specs is a really easy way to write human readable specifications and their implementations. What’s more, it can integrate with Junit and therefore existing CI/reporting mechanisms and existing testing libraries such as EasyMock.

However, what is perhaps most impressive is that all this ease of use and power can be applied to you Java code as much as any Scala code you have. There is nothing at all to stop you using Specs, or any Scala test framework, to test Java code. This is why testing is such a great vector for starting out with Scala and building skills. You can start to introduce it into an existing Java project orthogonally through your tests rather than diving straight into writing production code in Scala.

Leave a Reply