Writing elegant unit tests with the Spock framework
Published on: Author: Ronald Bakker Category: Java & WebAs a programmer, you want to write elegant, readable and manageable code that you’re proud of. But what’s the deal with your test code? Doesn’t the same apply there? This article shows why Groovy and the Spock framework are perfect for writing elegant and readable unit tests. Then you can be proud of them too….
There are countless frameworks, patterns and blogs that try to help you keep your own code readable and manageable. Yet your test code has a totally different structure and therefore has its own frameworks and patterns. But is Java the most suitable language for unit tests?
Take the following unit test in Java:
private final List<DayOfWeek> weekDays = List.of( MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY ); private final Map<List<DayOfWeek>, OpenTimes> regularHours = Map.of( weekDays, OpenTimes.parse("08:00-17:00"), List.of(SATURDAY), OpenTimes.parse("08:00-14:00") ); private final Map<LocalDate, OpenTimes> holidayHours = Map.of( LocalDate.parse("2021-12-24"), OpenTimes.parse("08:00-16:00"), LocalDate.parse("2021-12-25"), OpenTimes.CLOSED_ALL_DAY ); private final OpeningHoursProvider underTest = new OpeningHoursProvider(regularHours, holidayHours); @Test void shouldProvideCorrectOpeningHoursOn24DecemberAt10AM() { // given var dec24 = LocalDate.of(2021, 12, 24); var dec24At10AM = dec24.atTime(10, 0); // when var openingHours = underTest.forDateTime(dec24At10AM); // then var expectedTimes = OpenTimes.parse("08:00-16:00"); assertEquals(new OpeningHours(dec24, expectedTimes, Status.OPEN), openingHours); }
This is a test for a class that delivers opening times, assuming the regular opening times and different opening times on holidays. The test checks whether the shop is indeed closed one hour earlier on 24 December.
Groovy and the Spock framework can make this unit test more readable and easier to manage in a number of areas.
Method name for the test
A unit test starts with a name. It’s important that the name indicates clearly what the unit test is for. In the example, ‘should’ naming is used: the name indicates the desired behaviour to be tested – i.e. what the code ‘should’ do.
With ‘should’ naming, you often use a full sentence to indicate what the test does. Examples include: ‘shouldSaveToDatabase’ or ‘shouldThrowExceptionIfDatabaseIsDown’.
An example in Groovy is that you can use real strings when naming your methods. In other words, you can also use spaces, which improves the legibility. You can now give your test method the following, more readable name:
“should provide correct opening hours on 24 December at 10AM”
Specifications
In the Spock framework, a unit test is called a specification. This is because a test can actually be far more than just a check of whether your code is working properly. A good test also describes the features of you code. In other words: a specification.
Behaviour-driven
A unit test often consists of the following steps:
1. Create an initial situation [given]
2. Call the code being tested [when]
3. Check that the situation that arises is as expected [then]
A test is more readable if these steps are also visible. You can indicate this yourself by using single-line comments between your code, as was done in the Java example. But the Spock framework has standard labels you can use in your test. For each of the standard labels you can also describe the step. This makes it even clearer which steps you are taking and what the specifications are for your feature.
def "should provide correct opening hours on 24 December at 10AM"() { given: “december 24 2021 at 10:00” def dec24 = LocalDate.of(2021, 12, 24) def dec24At10AM = dec24.atTime(LocalTime.parse("10:00")) when: “the opening times are requested” def openingHours = underTest.forDateTime(dec24At10AM) then: “the opening times are 08:00 until 16:00” def expectedTimes = OpenTimes.parse("08:00-16:00") and: “the shop should be open” openingHours == new OpeningHours(dec24, expectedTimes, OPEN) }
An additional feature is that you can use conditions in the ‘then:’ label that are used as assertions for your test. You could, for example, use the following condition:
openingHours == new OpeningHours(dec24, expectedTimes, OPEN)
Now it checks whether the opening times that are returned (variable openingHours) are the same as those you expect (the created OpeningHours object). In other words, it’s equivalent to the assertEquals from the Java unit test:
assertEquals(new OpeningHours(dec24, expectedTimes, Status.OPEN), openingHours);
Data-driven testing with data tables
In the example above, we did one test for the opening times on 24 December, but there are probably far more edge cases for which you want to write a test, such as other holidays, weekdays and multiple edge cases around the opening and closing times. You want the code to remain readable and you don’t want to have to duplicate code for these new tests.
The Spock framework offers data tables for this. It allows you to use the same test code for different inputs and outputs. You define it under a where: label. It could look as follows:
def "should provide correct opening hours on #dateStr at #timeStr"() { given: “a moment in time” def date = LocalDate.parse(dateStr) def dateTime = date.atTime(LocalTime.parse(timeStr)) when: “the opening times are requested” def openingHours = underTest.forDateTime(dateTime) then: “the opening times should be correct” def expectedTimes = OpenTimes.parse(expectedTimesStr) and: “the expected status should be correct” openingHours == new OpeningHours(date, expectedTimes, expectedStatus) where: “we use the following test data” dateStr | timeStr || expectedStatus | expectedTimesStr "2021-12-07" | "16:59" || OPEN | "08:00-17:00" "2021-12-07" | "17:00" || CLOSED | "08:00-17:00" "2021-12-11" | "19:15" || CLOSED | "08:00-14:00" "2021-12-24" | "14:00" || OPEN | "08:00-16:00" "2021-12-25" | "14:00" || CLOSED | null }
The same test is run with this code five times. Each time you use a different date and time (columns dateStr and timeStr) and each time there is a different expected result (expectedStatus and expectedTimesStr). You can use the column names in your test as variables. You can separate columns with input from the columns with expected results with a double upright dash.
You can also use the column names in the name of your test. This was done in the example with #dateStr and #timeStr. When you run the test, the values used are substituted in the test name. If the test fails, you can see exactly which variables were used in the test:
[ERROR] should provide correct opening hours on 2021-12-25 at 14:00 Time elapsed: 0.318 s <<< FAILURE!
In an IDE such as IntelliJ, you can also easily see which variables are used for the different tests, so you can quickly see which test fails:

Syntax for collections in Groovy
In a unit test, you often create lots of collections. For example, when setting up your starting situation or when creating the expected output. Java can be very cumbersome in terms of creating collections, although fortunately much has already been improved.
Groovy has numerous useful syntax improvements for collections that can make your unit far less verbose and therefore more readable. For example, you can initialize Lists and Maps with straight brackets and you can also easily define ranges that can also be used as Lists.
The section of the Java unit test in which the test instance (with the name underTest) is initialised could look as follows in Groovy:
def regularHours = [ (MONDAY..FRIDAY): OpenTimes.parse("08:00-17:00"),aven [SATURDAY]: OpenTimes.parse("08:00-14:00"), ] def specialHours = [ (LocalDate.parse("2021-12-24")): OpenTimes.parse("08:00-16:00"), (LocalDate.parse("2021-12-25")): OpenTimes.CLOSED_ALL_DAY, ] def underTest = new OpeningHoursProvider(regularHours, specialHours)
Using the Spock framework in your existing project
The Spock framework is an extensive test framework that includes everything you find in Java frameworks as JUnit. Mocking is part of the framework, so you don’t have to use an additional library for that.
The Spock framework can also readily be used in your existing Java or Kotlin project. You need two additional dependencies: org.codehaus.groovy:groovy-all and org.spockframework:spock-core. If you use maven, the GMavenPlus plugin is also really useful.
If you’re interested in the features of the Spock framework or of Groovy, take a look at these websites.
Conclusion
The fact that you don’t want to use too many different programming languages in your organisation is understandable. There are enough programmers who know Java, but are there enough Groovy programmers around?
It may be worth taking a look at the Spock framework. Groovy is a language that closely resembles Java, and any Java programmer should be able to write unit tests in Groovy without too many problems. Groovy runs on the Java platform, so you can use all the known Java libraries and you can use it seamlessly in your existing development environment.
Groovy seems to have several benefits compared to Java when it comes to writing readable, manageable unit tests. So now, you can finally write elegant unit tests too!
This artical was also published in Java Magazine, issue 2 #2022. Would you like to receive Java Magazine? Become a NLJUG member!