Testing Your API Assertions With the Spock Framework
Learn how to test your APIs with Spock, a comprehensive testing framework that uses Java and Groovy, leveraging the benefits of several existing Java frameworks.
Join the DZone community and get the full member experience.
Join For FreeSpock is a testing framework that uses the Java and Groovy programming languages. It's a comprehensive testing framework, covering everything you need for unit testing, BDD, and Mocks. Of course, you can test your APIs with Spock.
Created in 2008 by Peter Niederwieser from Gradle, who was later joined by Luke Daley (also from Gradle), the first release of Spock was in 2015. Since then, it has been used as the default test framework in Grails, as well as being used internally by Gradle, Groovy, MongoDB, Tapestry, Netflix, and more.
Spock leverages the benefits of existing Java frameworks: JUnit, TestNG, JBehave, Cucumber, Mockito, JMock, and EasyMock. Spock's main strength is that it has only one dependency. Therefore, users don't need different dependencies for each of these tools. Instead, they have one for Spock.
In addition, the LOC (lines of code) produced by Spock are much shorter, which makes Spock tests much more readable and maintainable.
Let's look at an example. Given we have a Java class that check files extensions:
public class FileExtensionValidator() {
public boolean isValidExtension(Srting fileName) {
...
}
}
The body of the method is not important for us, so for saving space I exchanged it with "...".
Now, let's cover this class with a Spock test.
@Unroll("Checking file name #pictureFile")
def "All kinds of JPEG file are accepted"() {
given: "an file extension checker"
FileExtensionValidator validator = new FileExtensionValidator()
expect: "that all jpeg filenames are accepted regardless of case"
validator.isValidExtension(pictureFile)
where: "sample image names are"
pictureFile << GroovyCollections.combinations([["sample.","Sample.","SAMPLE."],['j', 'J'], ['p', 'P'],['e','E',''],['g','G']])*.join()
This Spock class is 10 LOC and results in 72 test scenarios. Want to try doing the same in JUnit? Go ahead, but if you work with JUnit, you know that the same test would be much longer, roughly up to 3 times more!
Another big pro for Spock is that it uses the JUnit runner. This means that if you've already been running your tests with it, you can keep doing so, and you don't need to learn how to use a new runner! In addition, this makes your tests compatible with all existing JUnit tools.
Last but not least, Spock is written in Java and has a Groovy front-end (same as Gradle). This means that most of Java's functionalities can be used as is, in Spock and Groovy tests or projects.
However, to use Spock you have to learn a new language, Groovy. That being said, Groovy is an easy to learn language. Also, it's possible to use Java-like code for the first times you work with Groovy, until you become more professional at it.
Now let's look at how you can test your API assertions with Spock.
1. Add the required dependencies to our build.gradle file:
In the plugins section -
apply plugin: 'groovy'
In the dependencies section -
compile group: 'org.codehaus.groovy.modules.http-builder', name: 'http-builder', version: '0.7.1'
testCompile group: 'org.spockframework', name: 'spock-core', version: '1.1-groovy-2.4'
testCompile group: 'org.spockframework', name: 'spock-spring', version: '1.1-groovy-2.4'
That's it! Spock is now enabled for our project. If you are using IntelliJ IDEA you will see that you now have two newly generated Groovy folders, in "main" and "test" (see picture):
2. Put your test Groovy class in the src/test/groovy folder on GitHub and name it ArrivalControllerTest. As you can guess, it will test the ArrivalController java class.
Let's take a look at what we have inside that test class:
import com.demo.BlazeMeterApi
import groovyx.net.http.RESTClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import static com.demo.constant.Paths.ARRIVAL
import static com.demo.constant.Paths.VERSION
import static io.restassured.http.ContentType.JSON
@SpringBootTest(
classes = BlazeMeterApi.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT
)
@ActiveProfiles(value = "test")
@ContextConfiguration
class ArrivalControllerTest extends Specification {
RESTClient restClient = new RESTClient("http://localhost:16666", JSON)
def "Check Arrivals controller"() {
restClient.auth.basic 'blaze', 'Q1w2e3r4'
when: "get all arrivals"
def response = restClient.get(
path: VERSION + ARRIVAL + 'all',
requestContentType: JSON
)
then: "Status is 200"
response.status == 200
and: "Body contains proper values"
assert response.data[0].id == 1
assert response.data[0].city == 'Buenos Aires'
assert response.data.size == 7
}
}
Now let's understand what is going on inside this test.
@SpringBootTest(
classes = BlazeMeterApi.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT
)
@ActiveProfiles(value = "test")
@ContextConfiguration
This part of the code is responsible for:
- Ramping up a Spring Context with a defined port and the main class BlazeMeterApi.class
- Making sure this file will work only on test profiles
RESTClient restClient = new RESTClient("http://localhost:16666", JSON)
This is a simple REST client for interacting with API endpoints.
def "Check Arrivals controller"() {
restClient.auth.basic 'blaze', 'Q1w2e3r4'
when: "get all arrivals"
def response = restClient.get(
path: VERSION + ARRIVAL + 'all',
requestContentType: JSON
)
then: "Status is 200"
response.status == 200
and: "Body contains proper values"
assert response.data[0].id == 1
assert response.data[0].city == 'Buenos Aires'
assert response.data.size == 7
}
This part is the actual test. In this test scenario we are calling the VERSION + ARRIVAL + 'all' endpoint, and then asserting the status and part of the response body as a JSON with items.
- The method
def "Check Arrivals controller"()
is a fully qualified String message. This will be visible in execution and make it easier to find out what is going on in the test, step by step. - When - BDD Gherkin language. In this part of test we are calling the API endpoint.
- Then/And - BDD Gherkin language. This is the part that performs the assertions. In this case: assert response.data[0].id == 1 and assert response.data[0].city == 'Buenos Aires'.
Please note that there are no assertions like in JUnit; rather, there is only one assert that will make sure the equation is valid.
4. Finally, let's look at the execution results when everything is going well. On the left side, you can see the name that we used in the Spock test as a method name.
Now, let's look at a case when the test fails, so we can see the power of Spock assert.
So I changed the id to 2, added a whitespace in the Buenos Aires and changed 7 to 8. After the execution, I got this error message:
Condition not satisfied:
response.data[0].id == 2
| | | | |
| | | 1 false
| | [id:1, city:Buenos Aires]
| [[id:1, city:Buenos Aires], [id:2, city:Rome], [id:3, city:London], [id:4, city:Berlin], [id:5, city:New York], [id:6, city:Dublin], [id:7, city:Cario]]
groovyx.net.http.HttpResponseDecorator@943cf5
Expected :2
Actual :1
As you can see, in other cases you would only have a message that the Assertion failed. However, Spock provides you with everything you need to debug and fix the test, even without debugging the test/application itself. We can immediately see that the id of the item with index 0 cannot be 2 because Spock provided us with the list of items as proof.
This is very important, because if you are working in big enterprise projects, you know that ramping up an environment is sometimes very expensive, and even though the test and fix only require 5-10 minutes, the whole process will take hours.
Published at DZone with permission of , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments