technology from back to front

Stackable traits for ScalaTest test suites

When your application is based on Spring it makes a lot of sense to fire up a Spring context within your integration tests and functional tests.

For a particular Scala-based project it was necessary to manage not only the lifetime of the Spring context, but also the lifetime of an annotation-based REST library component called Jersey, which works together with Spring.

Each of these have their own setup and teardown actions. Some tests require just Spring, and others require both Jersey and Spring to be started (and stopped). It gets even more involved because there’s the choice of starting and stopping either of these options before and after the whole test suite, or before and after every test.

Yes, it seems like a lot of work, but the results are well worth it: it means we can test the behaviour of the Jersey REST API by setting expectations on Spring components just before invoking API calls.

It turns out that there’s an elegant way in which Scala’s traits can express these various startup / teardown behaviours through stackable modifications. Traits isolate cross-cutting concerns and further allow them to be declaratively mixed-in to test suites that with them as needed (Scala jargon, think implements in Java!). Using the approach we’re also explicit about how Jersey is started after Spring is started, remembering the order in which traits are mixed into a class does matter!

We didn’t start off with that approach though…

Before…

So when I look at this problem, I see it as a number of orthogonal concerns:

  • Before & After Tests vs. Test Suite
  • Spring vs. Spring+Jersey
  • Start/Stop Sequencing

Fortunately the first of these is already taken care of. ScalaTest authors have taken care to provide the BeforeAndAfter[Each] and BeforeAndAfterAll traits, an idiomatic way to specify whether the specified code should run before/after every test or before/after all tests in the suite.

The code sure did make use of these, but in wholly the wrong way. Spring-related tests were made to extend a custom subclass of FunSpec:

class SpringContextSpec extends FunSpec {
  var ctx: ApplicationContext = _

  def startSpring() {
    ctx = new ClassPathXmlApplicationContext("classpath*:/applicationContext-test.xml")
  }

  def stopSpring() {
    ctx.destroy()
  }

  // Interesting auto-wired bean definitions as Scala vals...
  // Helper functions...
  // Spring-related oddities...
}

This is somewhat undesirable, because in every test suite it is necessary to explicitly call startSpring() and stopSpring(), like so:

class NotSoAwesomeTestSuite1 extends SpringContextSpec with BeforeAndAfter {
    before {
        startSpring()
    }

    after {
        stopSpring()
    }

    // Here be dragons
}

The reason that we want to do this is because startSpring() and stopSpring() is useful for beforeAll() and afterAll() as well.

class NotSoAwesomeTestSuite2 extends SpringContextSpec with BeforeAndAfterAll

    override def beforeAll() {
        startSpring()
    }

    override def afterAll() {
        stopSpring()
    }

    // Here be dragons
}

Both of these kinds of TestSuite do indeed occur. The boilerplate is ugly, but it is necessary under this design. The only way to hide the code’s hideousness is to introduce two new abstractions called SpringContextSpecEach and SpringContextSpecAll and have tests inherit from those accordingly.

And what if some tests needed Jersey? Say they extended a class called JerseySpringContextSpec defined like below:

class JerseySpringContextSpec extends SpringContextSpec {
    startJersey()
    stopJersey()
    // ...
}

Then JerseySpringContextSpec too would require its own ‘Each’ and ‘All’ subclasses: JerseySpringContextSpecEach JerseySpringContextSpecAll etc. In doing so we would be advocating an inflexible inheritance-based solution, exposing ourselves to a combinatorial explosion of noisy and confusing subclasses. It’s just plain nasty.

Stackable traits to the rescue.

After…

Ideally we want all of that behaviour directly configurable in the test suite declaration like the below. Square brackets and pipe (|) denote option and alternative respectively:

class IdealTestSuite extends FunSpec with BeforeAndAfter(Each|All) with Spring [with Jersey] with ... {
    // Here be quite genuinely useful tests
}

… and have the code do exactly the right thing before and after as we’d expect. This declaration can also ensure that Spring is started before Jersey.

That’s the theory anyway. Practise is often another wildly different beast. Though you’ll be pleased to know that my solution for this was actually incredibly close to the ideal:

class RealTestSuite extends FunSpec with StartStopBeforeAndAfter(Each|All) with (Spring|Jersey) with ... {
    // Here be quite genuinely useful tests
}

Not only is it more readable, it is much more declarative. It allows the programmer to change the execution behaviour of the test suite by changing 4 characters!

The key here is a special trait that I haven’t shown you yet. It’s called StartStop and its job is to represent the concept of ’something that can be started and stopped’.

trait StartStop {
  def start() {}
  def stop() {}
}

The implementations of start() and stop() do nothing at the moment but very soon they will be given proper meaning. Now we’d like to say connect this to the two sorts of before/after:

trait StartStopBeforeAndAfterEach extends Suite with BeforeAndAfterEach with StartStop {
  override def beforeEach() { start() }
  override def afterEach() { stop() }
}

and

trait StartStopBeforeAndAfterAll extends Suite with BeforeAndAfterAll with StartStop {
  override def beforeAll() { start() }
  override def afterAll() { stop() }
}

Great; so start() and stop() are now invoked before/after each/all as we please. They still do nothing at the moment, so now is a good time to imbue meaning upon the start() and stop() methods.

Start and stop Spring:

trait Spring extends StartStop {
  var ctx: ApplicationContext =

  def springContext = new ClassPathXmlApplicationContext("classpath*:/applicationContext-test.xml")

  abstract override def start() {
    super.start()
    ctx = springContext
  }

  abstract override def stop() {
    ctx.destroy()
    super.stop()
  }
  // Spring-specific fields and methods
}

… and start and stop Jersey (necessarily after Spring):

trait Jersey extends StartStop with Spring {

  var jerseyTest: JerseyTest = null

  abstract override def start() {
    super.start()
    jerseyTest = new JerseyTest(new LowLevelAppDescriptor.Builder("net.lshift.very.important.project").build()) {
      override def getTestContainerFactory = new JerseyInMemoryContainerFactory(ctx)
    }

    jerseyTest.setUp()
  }

  abstract override def stop() {
    jerseyTest.tearDown()
    super.stop()
  }

  // Jersey-specific fields and methods
}

Et voilĂ . What clear-cut code!

What we’ve done here is separated out the concerns just by using Scala traits: remarkably there are no annotations to be seen. Had this been Java we’d have to use a language extension like AspectJ.

The code also describes that Jersey initialisation necessarily follows Spring initialisation thanks to the way trait linearisation works.

Much of the magic comes from a combination of the abstract override def (start|stop) declaration and calls to super.(start|stop)(). When you mix-in a trait that has abstract override methods in it, you’re expecting an existing implementation of that method already on your class stack. This is why is important to provide a no-op implementation of start() and stop() methods in StartStop trait: so that they can be overridden!

The super.(start|stop)() calls form form a chain of invocations, just like reading along the class declaration line from right to left. This ensures everything is started and stopped in the right order. No more horrible startSpring() and stopSpring() methods to be seen.

For more details on delinearization and how stackable modifications actually work under the covers, I recommend the chapter on Programming in Scala.

Disclaimer

I don’t particularly recommend using Spring with Scala by the way. Spring being Spring, there is the official ‘Spring way’ of writing and structuring tests, but then we stuck to the annotation-free real-world. If you find that the above code can work better with annotation-based Spring configuration, please do that instead of the way we manage the ClassPathXmlApplicationContext explicitly.

And anyway, if all you’re using Spring for is just DI, consider tools native to Scala first, like Subcut.

Hope this helps!

by
hok
on
24/11/12
 
 
2000-13 LShift Ltd, 1st Floor Office, Hoxton Point, 6 Rufus Street, London, N1 6PE, UK +44 (0)20 7729 7060