Write one test and get 40 for free
I build a Java port of the LESS CSS compiler, originally written in JavaScript.The LESS compiler already has more than 40 test cases that compare a compiled LESS file with the expected CSS result. I only had to create one unit test that loops over all LESS files, compiles them and checks the result.
def "less compatibility tests"() { expect: compiler.compile(lessFile) == getCss(lessFile) where: lessFile << new File('test/less').listFiles().findAll { it.name.endsWith('.less') } } def getCss(File lessFile) { return new File('test/css' + lessFile.getName().replace('.less', '.css')).text }
Notice how you can tell Spock that it has to create one test for each variable in a collection:
where: variable << collection
Also notice the absence of assertEquals in the expect block: we only need to write boolean conditions. As programmers (and even non-programmers), we are trained to recognize these comparisons, so they are easier to read, understand and maintain then the classical assertXxx method calls.
Test isolation
We could also write a plain JUnit test that loops over the files to test them, but the problem would then be test isolation: if one test fails, the test method is aborted and we won't know the status of the remaining tests.Spock, instead, runs an isolated test for each iteration. All failures are reported separately, but if all tests pass, you will see only one method in the test report.
In the above example, it would be even better to have one test method with a descriptive name for each LESS file. This is what Spock's Unroll annotation does:
@Unroll def "#lessFile.name compatibility test"() {...}
By using #lessFile.name in the method name, we get clear test reports:
Data tables
Spock where blocks are not limited to one variable. If you want to populate more than one variable in each iteration, you can use the collection approach as shown above, or you can provide a table with values:when: def reader = new FileSystemResourceReader() then: reader.canRead(location) == canRead where: location | canRead 'file1.txt' | true '../file2.txt' | true '/no file' | false
This example will run 3 tests: one for each data row.
Data tables are especially useful for testing the typical use cases and corner cases in one go.
Syntactically, a data table is a bunch of unrelated or statements, so it may look strange at first sight. But if well formatted, data tables look like tables, and that's how Spock interprets them.
Intellij IDEA users get an extra here: it knows about Spock data tables and can format them automatically.