Enterprise testing with Spock – Part I
At Shutl, as an eBay company, I have had the opportunity to collaborate with different teams that are developing Java backend services, usually using JUnit and Mockito for their tests. I’ve also had the opportunity to meet people with a solid background in other tech stacks such as Ruby, Scala, Clojure, etc.
And the teams and individuals that have experience with more than one tech stack usually agree that when it comes to writing tests in Java with the combo of JUnit + Mockito, is common to see tests that are difficult to read and maintain and that it is easy to end up fighting against these tools instead of using them seamlessly. So in this blog post I am going to introduce Spock, which I believe it is a really good alternative to these common issues.
Spock is a testing framework that allows you to write tests for applications based on languages like Java and Groovy. You can find more details about the project on GitHub. Spock provides a DSL that makes easy to write highly expressive test cases, its JUnit runner allows you to run tests within your IDE although it can also be used in combination with other build tools such as Maven or Gradle to run your project’s test suite and it also has a really good integration with projects based on Spring / Spring boot.
Let’s take a look at an example of a test written in Spock and see what it looks like.
Imagine that we want to develop a Java REST API that finds an existing user in our system, we could write a test that would look like this:
import spock.lang.Specification class UserControllerSpec extends Specification { void 'retrieves a user'() { given: def controller = new UserController() when: def response = controller.show(1) then: response == [id: 1, name: 'David'] } }
The convention for giving a name for the test class follows this rule: SubjectUnderTest followed by the Spec suffix, in our case the SUT is the UserController, and it has to extend from spock.lang.Specification. Also, note that the name of the test is a String so we can define our test cases in a human readable way.
Since Groovy can be optionally typed, and Spock is based on Groovy, there is no need to write strongly typed code like UserController controller = new UserController().
Spock follows a BDD style, so it’s common to find tests structured in 3 main blocks Given/When/Then. So Given some preconditions, When we execute an action in our system, Then we want to verify that something happened. For this particular case, when we have an instance of our controller, and, we call the show
method passing to it the id 1 as an argument, then it returns a response that contains the user with the id equal to 1.
Note also that every line of code that goes after the then
block is an implicit assertion, so if that statement evaluates to true it will consider the test execution as successful, on the other hand, if it evaluates to false, the test will fail.
Even if this style looks quite readable, Spock offers a better way to make simple tests like the one above, more concise and expressive, so we can refactor our test and write it in a better way:
import spock.lang.Specification class UserControllerSpec extends Specification { def controller = new UserController() void 'finds a user'() { expect: controller.show(1) == [id: 1, name: 'David'] } }
Now if we are doing TDD probably we will create our UserController class, implement the show method and hard-code the return value, something like this will make our test green:
import com.google.common.collect.ImmutableMap; import java.util.Map; public class UserController { public Map show(Integer id) { return ImmutableMap .builder() .put("id", id) .put("name", "David") .build(); } }
Now, we know that if we add another test case, for a user with id = 2 and name = Sarah our test won’t pass. Spock has a feature call Data Driven Tests, that allows you to write different test cases in a really concise way. Let’s take a look at an example:
import spock.lang.Specification import spock.lang.Unroll class UserControllerSpec extends Specification { def controller = new UserController() @Unroll void 'finds a user'() { expect: controller.show(id) == [id: 1, name: 'David'] where: id | response 1 | [id: 1, name: 'David'] 2 | [id: 2, name: 'Sarah'] } }
So, the data that feeds the test lives in the where
section. If we don’t use the @Unroll annotation, the test will fail as a whole. However if we use it, we will know which particular test case is the one that is generating the failure. In our case, it is the last one, when the user id = 2.
The messages that Spock returns when we run a test and it fails are really helpful to identify what went wrong, and where is the failure coming from. This is the output for our test so far:
Condition not satisfied: controller.show(id) == response | | | | | 2 | [id:2, name:Sarah] | false [id:2, name:David]
At this point, we can see that despite the id
property in the response has the value that we expect, the name
property of the user is David instead of Sarah.
Now imagine that we want to have a fresh instance of the controller for each test execution because tests should be independent from each other and sharing state between test executions can be dangerous. We can refactor our test to achieve that level of isolation like this:
import spock.lang.Specification import spock.lang.Unroll class UserControllerSpec extends Specification { def controller void setup() { controller = new UserController() } @Unroll void 'finds a user'() { expect: controller.show(id) == response where: id | response 1 | [id: 1, name: 'David'] 2 | [id: 2, name: 'Sarah'] } }
The setup method will be executed for each test iteration, it works in a similar way as the @Before annotation in JUnit. Spock offers the following methods to setup and cleanup the state of your tests:
void setup() {} // run before every feature method void cleanup() {} // run after every feature method void setupSpec() {} // run before the first feature method void cleanupSpec() {} // run after the last feature method
In future articles, we will be exploring other Spock’s cool features, we will see how Spock provides different ways to create test doubles for our dependencies and we will be taking a look at how to do property testing by using another Spock related library.
By the way, you can use the online Spock web console to give the framework a try right now.
This article appeared first on Kenfos Blog