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:
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:
The reason that we want to do this is because startSpring() and stopSpring() is useful for beforeAll() and afterAll() as well.
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:
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:
… 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:
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’.
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:
and
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:
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):
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!
