Test your REST API with Spock – Introduction to Spock Framework
In this article I’d like to show you how to test your API with Spock Framework. Spock is a testing framework for Java and Groovy applications. It extends JUnit runner and let us write shorter and more readable code. Spock supports unit testing, BDD and Mocking. It is also great for Data Driven Testing.
Setting up Spock
Firstly, we need to create maven project and prepare pom.xml file by adding necessary dependencies.
The only one required dependency to run Spock tests is spock-core. I also added http-builder because I’d like to use RESTClient which is a part of this library.
<dependencies>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>${spock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy.modules.http-builder</groupId>
<artifactId>http-builder</artifactId>
<version>0.7.2</version>
</dependency>
</dependencies>
Basic example
After adding these dependencies we are able to write Spock tests using Groovy. For the first example I created a class ‘BasicAuthTest.groovy’ in directory src/test/groovy.
Let’s take a look at what we have inside this class:
import groovyx.net.http.HttpResponseException
import groovyx.net.http.RESTClient
import spock.lang.Shared
import spock.lang.Specification
class BasicAuthTest extends Specification {
@Shared
def client = new RESTClient( "$SERVER_URL:$SERVER_PORT")
def 'should return 200 code when used valid credentials' () {
when: 'login with invalid credentials'
client.headers['Authorization'] = "Basic ${"$USERNAME:$PASSWORD".bytes.encodeBase64()}"
def response = client.get( path : '/basic-auth/user/pass' )
then: 'server returns 200 code (ok)'
assert response.status == 200 : 'response code should be 200 when tried to authenticate with valid credentials'
}
def 'should return 401 (unauthorized) code when used invalid credentials' () {
when: 'login with invalid credentials'
client.headers['Authorization'] = "Basic ${"$USERNAME:$INVALID_PASSWORD".bytes.encodeBase64()}"
client.get( path : '/basic-auth/user/pass' )
then: 'server returns 401 code (unauthorized)'
HttpResponseException e = thrown(HttpResponseException)
assert e.response.status == 401: 'response code should be 401 when you use wrong credentials'
}
Each Spock class must extend Specification interface.
@Shared is a Spock annotation which means that each feature will use the same instance of variable.
We can use Strings as methods names. It is very convenient because these names will be displayed in execution results.
As you can see I used ‘When’ and ‘Then’ keywords because Spock allows us to use this Gherkin language to be able to write the BDD style code.
In ‘Given’ section we usually do some setup to use it in ‘When’ section. ‘When’ is a place where we actually run our tests and in ‘Then’ (or ‘And’) part we perform assertions.
Spock has this very convenient way to check if an exception is thrown by the methods invoked in a When block. We do not need to use try-catch. We can use ‘thrown()’ and ‘notThrown()’ methods to check if particular exception was or wasn’t thrown and we are also able to check the message by referring to this exception.
I also mentioned the usefulness of Spock’s ‘assert’ keyword. Let’s change expected response code in the first test from ‘200’ to ‘201’ and check the results.
If assertions are complicated or take a lot of lines of code, we can extract them to a method and as long as we use ‘assert’ keyword inside it the results will be displayed in this useful way.
As you can see in the below example we can use ‘And’ keyword to be able to add additional steps. I used it to check some more assertions.
@Shared
def client = new RESTClient( "$SERVER_URL:$SERVER_PORT")
@Ignore
def 'should return image metadata containing proper fields' () {
when: 'send request for image metadata'
def response = client.get( path : '/photos/1' )
then: 'server returns set of metadata for a single image'
assert response.status == 200 : 'response code should be 200'
assert response.contentType == 'application/json' : 'response should be in json format'
and: 'response contains all required fields'
assert response.data.albumId != null
assert response.data.id != null
assert response.data.title != null
assert response.data.url != null
assert response.data.thumbNailUrl != null
@Ignore annotation means that the specification (class) or feature will not be run. You can use it to skip your tests.
Data Driven Testing
Spock’s Data Driven Testing support let us exercise the same code multiple times with different inputs and expected results. It is possible thanks to Data Tables. Please notice that each Data Table must have at least two columns. If you need only one column, the second one should be filled with ‘_’ (dashes). Dash is an equivalent of any value.
def 'should return 400 code (bad request) if provided id parameter does not contain 3 digits' () {
when: 'send request with incorrect id to get album metadata'
client.get( path : '/albums', query : [id : idVal ] )
then: 'server returns 400 code'
HttpResponseException e = thrown(HttpResponseException)
assert e.response.status == 400: 'response code should be 400 if provided incorrect album id parameter'
where:
idVal | _
-1 |
0 |
1 |
15 |
99 |
0001 |
9999 |
99999999999991 |
}
In a ‘query’ parameter you can provide a query string which is a part of URL.
For the first request the URL will look like https://jsonplaceholder.typicode.com/albums?id=-1
Now look at the below example of sending POST request.
@Unroll
def 'should return 201 code (created) when trying to save record with all required fields' () {
when: 'try to save record with all required fields'
def response = client.post(
path: '/photos',
body: [albumId : albumIdVal,
id : idVal,
url : urlVal,
thumbnailUrl: thumbnailUrlVal],
requestContentType : JSON)
client.get( path : '/albums', query : [id : idVal] )
then: 'server returns 201 code (created)'
assert response.status == 201: 'response code should be 201 if provided all required parameters'
where:
albumIdVal | idVal | urlVal | thumbnailUrlVal
141 | 2 | 'http://placehold.it/600/32' | 'http://placehold.it/th/600/32'
101 | 6 | 'http://placehold.it/600/3122' | 'http://placehold.it/th/600/3122'
111 | 56 | 'http://placehold.it/600/23244' | 'http://placehold.it/th/600/23244'
}
A method annotated with @Unroll will have its iterations reported independently. Note that it does not affect the execution. It only changes the way the results are displayed in a report. Sometimes it is useful to see which case failed because we can find the cause of failure much faster.
Let’s see what will be displayed if one of three iterations failed. Example below shows the difference between using ‘Unroll’ annotation and not using it.
Without ‘Unroll’ – it combines all 3 iterations into one result.
With ‘Unroll’ – we can see which iteration failed.
Summary
Spock requires only one dependency which is spock-core. Spock doesn’t need all those assertions like JUnit to check results. It uses only one ‘assert’ keyword which provides very useful messages when the condition is not satisfied. Spock uses Gherkin-style syntax to define steps (given, when, then). Spock tests are written in Groovy which is a JVM-based scripting language with syntax similar to Java. Great thing about Groovy is that you can also write Java code and in most cases it will compile and work properly. For some people the main disadvantage of using Spock is that it needs Groovy. If your project uses Java, you will need extra dependency for Groovy. Also, the syntax can look weird if you got used to JUnit tests.