Moving up in the testing pyramid, you leave integration tests behind and reach func- tional tests. Functional tests, depicted in figure 7.8, view the whole system as a black box (in contrast with integration tests that deal with internal modules).
For non-interactive systems (those with no user-visible component), functional testing involves the testing of the services they provide to the outside world. In practice, this usually means testing the HTTP/REST endpoints of the back-end modules.
REST services use JSON or XML for the transport format. These examples use JSON. 7.3.1 Working with a simple REST service
A REST service is based on HTTP and a predefined message format (typically JSON or XML) and is implementation-agnostic. Even though the application in this example is a Spring-based one, it doesn’t matter to Spock. The application doesn’t even have to be a Java one. You can use Spock if you want to test the REST service of a Python or Ruby application.
The example application is the RESTAPI for the warehouse management example, as already discussed in the previous sections. Table 7.2 provides an overview of the endpoint and operations it supports (all responses are in JSON format).
Uses the Spring bean as before
Expected response
Test fails if these are different.
Actual response Live system
(replica of production)
Sample request JSON
JSON
Figure 7.8 A functional test sends a request and expects a certain response.
Like all applications shown so far, this back-end application was created for illustra- tion purposes. The application uses Spring MVC for the implementation, but this is completely irrelevant as far as functional tests are concerned. It could be imple- mented in any other framework or even programming language, as long as it accepts JSON messages over HTTP endpoints.
7.3.2 Testing REST services by using Java libraries
Writing a Spock test for REST services is straightforward. You can use any REST client library that you’re already familiar with. Many are available in the Java world, and at least for Spock tests, you should choose the one that you feel is more readable and compact.15
As a starting example, I’ve selected the Spring RestTemplate.16 The first test checks the /status endpoint (which returns a single string and not JSON), as shown in the next listing.
def "Simple status checker"() {
when: "a rest call is performed to the status page"
RestTemplate restTemplate = new RestTemplate()
String status = restTemplate.getForObject("http://localhost:8080/rest- service-example/status", String.class)
then: "the correct message is expected"
status == "Up and Running"
}
The takeaway from this trivial example is that because of Groovy/Java compatibility, you can use any Java REST client library you already use in your JUnit tests. Spock can use it without any extra modifications. It’s that simple!
Table 7.2 HTTP endpoints of example application
Endpoint GET POST PUT DELETE
/status Returns a success mes-
sage (“up and running”)
- -
/products Lists all products Creates a
default product
- Deletes all
products
/products/{id} Returns a specific product - - -
/products/{id}/name - - Renames a
product
-
15Such as RESTEasy (http://resteasy.jboss.org/), Jersey (https://jersey.java.net/), or Restlet (http://restlet .com/).
16See a tutorial on calling REST services from Spring at https://spring.io/guides/gs/consuming-rest/.
Listing 7.7 Testing REST services with Spock and Spring RestTemplate
Creates a Spring REST client
Performs a GET call on the / status endpoint
Examines the response of the REST call
209 Functional testing of REST services with Spock
7.3.3 Using the @Stepwise annotation to run tests in order
Now you’re ready to create additional tests for the business endpoints of your applica- tion. The next listing provides the whole Spock specification.
@Stepwise class SpringRestSpec extends Specification {
def "Simple status checker"() {
when: "a rest call is performed to the status page"
RestTemplate restTemplate = new RestTemplate() String status =
restTemplate.getForObject("http://localhost:8080/rest-service- example/status", String.class)
then: "the correct message is expected"
status == "Up and Running"
}
def "Cleaning all products"() {
given: "a rest call is performed that deletes everything"
RestTemplate restTemplate = new RestTemplate()
restTemplate.delete("http://localhost:8080/rest-service- example/products")
when: "a product list is requested"
List<Product> products =
restTemplate.getForObject("http://localhost:8080/rest- service-example/products", List.class)
then: "it should be empty"
products.size() == 0 }
def "Creating a product"() { given: "a rest template"
RestTemplate restTemplate = new RestTemplate() when: "a new product is created"
Product product =
restTemplate.postForObject("http://localhost:8080/rest- service-example/products","unused",Product.class)
and: "product list is requested again"
List<Product> products =
restTemplate.getForObject("http://localhost:8080/rest- service-example/products", List.class)
then: "it should have default values"
with(product) {
name == "A product"
stock == 0
Listing 7.8 Running multiple test methods in order
Ensures that all methods run in the order shown in the source file
Performs a DELETE call on the / products endpoint
Performs a GET call on the / products endpoint
Performs a POST call on the / products endpoint
Examines the JSON response
price == 0 weight == 0 }
and: "product list should contain it"
products.size() == 1 }
}
This listing includes test methods for the /products endpoint. The code should be familiar if you’ve ever worked with the Spring RestTemplate. There’s nothing Spock- specific inside the test methods. All code segments are Java statements that would work the same way in a JUnit test.
You should pay special attention, however, to the @Stepwise annotation at the top of the class. This Spock annotation comes in handy and does two things:
■ It makes sure that Spock will run all test methods in the order they’re defined in the Specification class.
■ During runtime, if any test method fails, those that come after it will be skipped.
The purpose of the @Stepwise annotation is to save you time when you have many functional tests. Although in theory all functional tests are independent, in practice this is rarely the case. For example, if the first test method fails (the one that checks the /status endpoint), the test environment is probably down, so there’s no point in running any more tests.
The @Stepwise annotation saves you time because you’re informed right away when something fails and can understand what the problem is more easily than when all tests fail. Figure 7.9 shows the runtime result with and without the @Stepwise annotation.
With @Stepwise annotation
Without @Stepwise annotation
Figure 7.9 The @Stepwise annotation skips subsequent test methods after a failure.
211 Functional testing of REST services with Spock
With the @Stepwise annotation enabled, you can see in two seconds that the test envi- ronment is down, instead of waiting four seconds for all tests to run (and fail). In a real enterprise project with hundreds of functional tests that may take several minutes (or even hours), the @Stepwise annotation is a lifesaver, as it drastically cuts the time of developer feedback after a failed build. With the @Stepwise annotation, you also get a clear indication if a bug failed because another precondition (contained in a previous test method) also failed.
7.3.4 Testing REST services using Groovy RESTClient
As with integration tests, the advantage of using Spock is that you’re not constrained to Java libraries; you can also use Groovy utilities. As an example, an alternative REST client can be used instead of the Spring RestTemplate.17
The following listing presents the same test as in listing 7.8, this time using the Groovy RESTClient.
@Stepwise class GroovyRestClientSpec extends Specification {
@Shared
def client = new RESTClient("http://localhost:8080/rest-service- example/") def "Simple status checker"() {
when: "a rest call is performed to the status page"
def response = client.get(path : "status") then: "the correct message is expected"
with(response) {
data.text == "Up and Running"
status == 200 }
}
def "Cleaning all products"() {
given: "a rest call is performed that deletes everything"
client.delete(path : "products") when: "a product list is requested"
def response = client.get(path : "products") then: "it should be empty"
with(response) {
data.isEmpty() status == 200
17A Groovy library for consuming REST services is found at https://github.com/jgritman/httpbuilder/wiki/
RESTClient.
Listing 7.9 Using Groovy RESTClient in a Spock test
Makes sure that all methods run in order
Creates a REST client
Performs a GET call on the /status endpoint
Examines the text response
Examines HTTP error code
Performs a DELETE call on the /products endpoint
} }
def "Creating a product"() {
when: "a new product is created"
def response = client.post(path : "products") and: "product list is requested again"
def listResponse = client.get(path : "products") then: "it should have default values"
with(response) {
data.name == "A product"
data.stock == 0 data.price == 0 status == 200 }
and: "product list should contain it"
listResponse.data.size() == 1 }
}
As you can see, the code is mostly the same. For each method call, you also check the HTTP error code, as it’s easy to verify with the RESTClient. As an exercise, feel free to write a functional test for the other endpoints of the application (the calls for renaming an existing product). The Groovy RESTClient has many more facilities, not shown in list- ing 7.9, that might be helpful in your own application should you choose to use it.