Have you worked someday at the project and you are so bored by writing the same kind of tests over and over? Alternatively, maybe you drop some test cases because the implementation of those is too complicated? Today I’ll show you the way to writing tests much faster and with less effort than with JUnit.
Why JUnit may be painful?
- You write same long java code over and over again
- Sometimes you are pushed to write 100 lines of test to cover 50 lines of a codebase
- Writing data-driven-tests is not obvious
We are going to talk about Spock, so maybe we should ask him what he thinks about writing tests in Spock Framework? :)
Spock installation - does it hurt?
Nope. It is straightforward and it doesn’t affect your existing JUnit tests - you can have Spock tests parallel with JUnit, and they work together, so don’t worry. To include the Spock framework in your existing project, you have to do two things:
- add groovy language support
- add Spock framework
Gradle
apply plugin: "groovy"
dependencies {
compile "org.codehaus.groovy:groovy-all:2.4.15"
testCompile "org.spockframework:spock-core:1.2-groovy-2.4"
// allows mocking of classes (in addition to interfaces)
testRuntime "net.bytebuddy:byte-buddy:1.8.21"
}
Maven
<dependencies>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.2-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.15</version>
<scope>test</scope>
</dependency>
<dependency> <!-- enables mocking of classes (in addition to interfaces) -->
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.8.21</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- The gmavenplus plugin is used to compile Groovy code. -->
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Only required if names of spec classes don't Surefire patterns (`*Test`) -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
<configuration>
<useFile>false</useFile>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
Now you should be able to add some tests at src/test/groovy
path.
Let’s Spock a little bit
We going to start with simple test:
class CalculatorTest extends Specification {
def 'should add two numbers'() {
given:
def calc = new Calculator();
def a = 5
def b = 10
when:
def result = calc.add(a, b)
then:
10 == result
}
}
As you can see each test case should extend Specification
class. To create test implementation, you need to specify a function (def keyword) with one description sentence and add at least one of when
and then
label. Simple, right? No more ugly function names like this:
@Test
public Given_UserIsAuthenticated_When_InvalidAccountNumberIsUsedToWithdrawMoney_Then_TransactionsWillFail() {
// ....
}
Okay so let’s say we would like to cover more cases, how we can do that? Spock allows us to write that kind of tests in a real fashion way.
class CalculatorTest extends Specification {
def 'should add two numbers'(int a, int b, int expectedResult) {
given:
def calc = new Calculator();
when:
def result = calc.add(a, b)
then:
expectedResult == result
where:
a | b || expectedResult
1 | 2 || 3
5 | 9 || 14
1 | 21 || 22
}
}
I guess everything is visible here even if you are not familiar with Spock or groovy language - right? Perfect. It is a reason why writing tests using Spock is so fast and easy - in my opinion writing tests in Spock is strict to the point.
Just for a mention groovy language works at the top of JVM, so you can use any Java object you wish to use.
As you can see we have covered a few cases with a tiny number of lines. I know, you can say now - We can do the same with JUnit @Parameters
annotation, and maybe you are right but … Spock tests - in my judgment - are still looking more clever.
Another example. Let’s say we have some service which calls external REST API and we need to be sure that it calls external API. Besides we would like to check if the data transfer object given as argument has valid input field inside.
class SuperServiceTest extends Specification {
def externalApi
def setup() {
externalApi = Mock(ExternalApiService)
}
def 'should call external api'(String[] someInput, int times) {
given:
def service = new SuperService(externalApi)
when:
service.someAction(someInput)
then:
times * externalApi.sendInput(args -> {
def externalApiDTO = args[0] as ExternalApiDTO
assert externalApiDTO.input == someInput
})
where:
someInput || times
null || 0
{} || 0
{'abc', 'def'} || 1
{'abc', 'def', 'yyy'} || 1
}
}
It is pretty simple, isn’t it? In the above example:
- We’ve checked if our external API call has executed
- We’ve covered cases for empty and null input
- We’ve ensured that created DTO contains valid data
Spock labels
Everything inside Spock test is separated with labels, and each label has different execution meaning.
// Variables, mocks and under test object initialization
given:
def xxx = yyy
// Provides test action, for example service call.
when:
service.fire()
// Verification of provided action or results assertion
then:
1 * foo.bar()
1 * repository.find() >> Mock(Entity) // We can provide returned value here too
// The 'expect' label is similar to 'then' but you are not able to verify executions.
// It's good point to check assertion.
// It must be followed by 'and' or 'then' label
expect:
1 == result.length
'abc' == result[0]
where
data-driven table - this is a reason why I love the Spock framework. The first row is a data table header where you should put case variables names. Other rows are just data you provide for each case.
where:
a | b || expectedResult
1 | 2 || 3
5 | 9 || 14
1 | 21 || 22
You may be wonder about that double pipe - it has an only visual difference - test execution is not affected by using a single or double pipe.
cleanup
In theory :cleanup should be used to free resources used by our test, but in real life, you probably won’t need it (unit testing - no I/O) - may be for some integration like tests. The cleanup: section must be followed by a where block, and may not be repeated.
cleanup:
something.close()
There is one more, the setup: label, but in my opinion, it is useless - we have given:, after all. Oh, and there is only one requirement: at least one when and then labels are required in a single test.
Everything you need is Mock…
Last examples in this article I show you how simple may be the object mocking. Two functions provide object mocking in Spock: Mock
for mocking and Spy
for spying.
The mocking mechanism works very similar to good know mocking frameworks like Mockito so I’ll not write about what means mocking in general instead of that I’ll show you some examples.
The first example is adding a fake return value after method execution.
def employee = Mock(EmployeeEntity)
employee.id >> UUID.randomUUID() // Groovy does not need full getter/setter signature,
// you can just write obj.id which means the same as obj.getId()
What do you think? It’s pretty sweet, right? :) We can go to the next one, let’s add fake method invocation on the Spy object
given:
def service = Spy(SuperService)
service.doSomething(_, _) >> { args ->
// do something
}
Those _, _
are invocation types wildcards that mean you are accepting .doSomething
function invocation with any parameters. If you would like to be more specific you have to specify needed types, like that:
given:
def service = Spy(SuperComparatorService)
service.compareStringWithNumber(_ as String, _ as Number) >> { args ->
return args[0].equals(args[1])
}
Okay, I hope I’ve shown some good points of Spock framework, and I’ll come back to it in the future for sure. From my experience, I would say when I’ve started using Spock for testing I cannot stop :)