I just announced the Master Class of my "REST With Spring" Course:
1. Introduction
REST-assured was designed to simplify the testing and validation of REST APIs and is highly influenced by testing techniques used in dynamic languages such as Ruby and Groovy.
The library has solid support for HTTP, starting of course with the verbs and standard HTTP operations, but also going well beyond these basics.
In this guide, we are going to explore REST-assured and we’re going to use Hamcrest to do assertion. If you are not already familiar with Hamcrest, you should first brush up with the tutorial: Testing with Hamcrest.
Let’s dive in with a simple example.
2. Simple Example Test
Before we get started ensure that your tests have the following static imports:
io.restassured.RestAssured.* io.restassured.matcher.RestAssuredMatchers.* org.hamcrest.Matchers.*
We’ll need that to keep tests simple and have easy access to the main APIs.
Now, let’s get started with the simple example – a basic betting system exposing some data for games:
{ "id": "390", "data": { "leagueId": 35, "homeTeam": "Norway", "visitingTeam": "England", }, "odds": [{ "price": "1.30", "name": "1" }, { "price": "5.25", "name": "X" }] }
Let’s say that this is the JSON response from hitting the locally deployed API – http://localhost:8080/events?id=390. :
Let’s now use REST-assured to verify some interesting features of the response JSON:
@Test public void givenUrl_whenSuccessOnGetsResponseAndJsonHasRequiredKV_thenCorrect() { get("/events?id=390").then().statusCode(200).assertThat() .body("data.leagueId", equalTo(35)); }
So, what we did here is – we verified that a call to the endpoint /events?id=390 responds with a body containing a JSON String whose leagueId of the data object is 35.
Let’s have a look at a more interesting example. Let’s say you would like to verify that the odds array has records with prices 1.30 and 5.25:
@Test public void givenUrl_whenJsonResponseHasArrayWithGivenValuesUnderKey_thenCorrect() { get("/events?id=390").then().assertThat() .body("odds.price", hasItems("1.30", "5.25")); }
3. REST-assured Setup
If your favorite dependency tool is Maven, we add the following dependency in the pom.xml file:
<dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>3.0.0</version> <scope>test</scope> </dependency>
To get the latest version, follow this link.
In addition, we may prefer to validate the JSON response based on a predefined JSON schema. In this case, we need to also include the json-schema-validator module in the pom.xml file:
<dependency> <groupId>io.rest-assured</groupId> <artifactId>json-schema-validator</artifactId> <version>3.0.0</version> </dependency>
To ensure you have the latest version, follow this link.
We also need another library with the same name but a different author and functionality. It is not a module from REST-assured but rather, it is used under the hood by the json-schema-validator to perform validation.
Let’s add it like so:
<dependency> <groupId>com.github.fge</groupId> <artifactId>json-schema-validator</artifactId> <version>2.2.6</version> </dependency>
It’s latest version can be found here.
The library, json-schema-validator, may also need the json-schema-core dependency:
<dependency> <groupId>com.github.fge</groupId> <artifactId>json-schema-core</artifactId> <version>1.2.5</version> </dependency>
and the latest version is always found here.
REST-assured takes advantage of the power of Hamcrest matchers to perform it’s assertion, so we must include that dependency as well:
<dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-all</artifactId> <version>1.3</version> </dependency>
The latest version will always be available at this link.
4. JSON Schema Validation
From time to time it may be desirable, without analyzing the response in detail, to know first-off whether the JSON body conforms to a certain JSON format.
Let’s have a look at an examples. Assume the JSON introduced in the above example has been saved in a file called event_0.json, and the file is present in the classpath, we can then use this JSON as a definition of our schema.
Then assuming that this is the general format followed by all data returned by our REST API, we can then check a JSON response for conformance like so:
@Test public void givenUrl_whenJsonResponseConformsToSchema_thenCorrect() { get("/events?id=390").then().assertThat() .body(matchesJsonSchemaInClasspath("event_0.json")); }
Notice that we’ll still statically import matchesJsonSchemaInClasspath from io.restassured.module.jsv.JsonSchemaValidator just as we do for all other methods.
5. JSON Schema Validation Settings
5.1. Validate a Response
The json-schema-validator module of REST-assured allows us the power to perform fine grained validation by defining our own custom configuration rules.
Say we want our validation to always use the JSON schema version 4:
@Test public void givenUrl_whenValidatesResponseWithInstanceSettings_thenCorrect() { JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.newBuilder() .setValidationConfiguration( ValidationConfiguration.newBuilder() .setDefaultVersion(SchemaVersion.DRAFTV4).freeze()) .freeze(); get("/events?id=390").then().assertThat() .body(matchesJsonSchemaInClasspath("event_0.json") .using(jsonSchemaFactory)); }
We would do this by using the JsonSchemaFactory and specify the version 4 SchemaVersion and assert that it is using that schema when a request is made.
5.2. Check Validations
By default, the json-schema-validator runs checked validations on the JSON response String. This means that if the schema defines odds as an array as in the following JSON:
{ "odds": [{ "price": "1.30", "name": "1" }, { "price": "5.25", "name": "X" }] }
then the validator will always be expecting an array as the value for odds, hence a response where odds is a String will fail validation. So, if we would like to be less strict with our responses, we can add a custom rule during validation by first making the following static import:
io.restassured.module.jsv.JsonSchemaValidatorSettings.settings;
then execute the test with the validation check set to false:
@Test public void givenUrl_whenValidatesResponseWithStaticSettings_thenCorrect() { get("/events?id=390").then().assertThat().body(matchesJsonSchemaInClasspath ("event_0.json").using(settings().with().checkedValidation(false))); }
5.3. Global Validation Configuration
These customizations are very flexible, but with a large number of tests we would have to define a validation for each test, this is cumbersome and not very maintainable.
To avoid this, we have the freedom to define our configuration just once and let it apply to all tests.
We will configure the validation to be unchecked and to always use it against JSON schema version 3:
JsonSchemaFactory factory = JsonSchemaFactory.newBuilder() .setValidationConfiguration( ValidationConfiguration.newBuilder() .setDefaultVersion(SchemaVersion.DRAFTV3) .freeze()).freeze(); JsonSchemaValidator.settings = settings() .with().jsonSchemaFactory(factory) .and().with().checkedValidation(false);
then to remove this configuration call the reset method:
JsonSchemaValidator.reset();
5. Anonymous JSON Root Validation
Consider an array that comprises of primitives rather than objects:
[1, 2, 3]
This is called an anonymous JSON root, meaning that it has no key-value pair nevertheless it is still valid JSON data.
We can run validation in such a scenario by using the $
symbol or an empty String ( “” ) as path. Assume we expose the above service through http://localhost:8080/json then we can validate it like this with REST-assured:
when().get("/json").then().body("$", hasItems(1, 2, 3));
or like this:
when().get("/json").then().body("", hasItems(1, 2, 3));
6. Floats and Doubles
When we start using REST-assured to test our REST services, we need to understand that floating point numbers in JSON responses are mapped to primitive type float.
The use of float type is not interchangeable with double as is the case for many scenarios in java.
Case in point is this response:
{ "odd": { "price": "1.30", "ck": 12.2, "name": "1" } }
assume we are running the following test on the value of ck:
get("/odd").then().assertThat().body("odd.ck", equalTo(12.2));
This test will fail even if the value we are testing is equal to the value in the response. This is because we are comparing to a double rather than to a float.
To make it work, we have to explicitly specify the operand to the equalTo matcher method as a float, like so:
get("/odd").then().assertThat().body("odd.ck", equalTo(12.2f));
7. XML Response Verification
Not only can you validate a JSON response you can validate XML as well.
Let’s assume we make a request to http://localhost:8080/employees and we get the following response:
<employees> <employee category="skilled"> <first-name>Jane</first-name> <last-name>Daisy</last-name> <sex>f</sex> </employee> </employees>
We can verify that the first-name is Jane like so:
@Test public void givenUrl_whenXmlResponseValueTestsEqual_thenCorrect() { post("/employees").then().assertThat() .body("employees.employee.first-name", equalTo("Jane")); }
We can also verify that all values match our expected values by chaining body matchers together like so:
@Test public void givenUrl_whenMultipleXmlValuesTestEqual_thenCorrect() { post("/employees").then().assertThat() .body("employees.employee.first-name", equalTo("Jane")) .body("employees.employee.last-name", equalTo("Daisy")) .body("employees.employee.sex", equalTo("f")); }
Or using the short hand version with variable arguments:
@Test public void givenUrl_whenMultipleXmlValuesTestEqualInShortHand_thenCorrect() { post("/employees") .then().assertThat().body("employees.employee.first-name", equalTo("Jane"),"employees.employee.last-name", equalTo("Daisy"), "employees.employee.sex", equalTo("f")); }
8. XPath for XML
We can verify our responses using XPath. Consider the example below that executes a matcher on the first-name:
@Test public void givenUrl_whenValidatesXmlUsingXpath_thenCorrect() { post("/employees").then().assertThat(). body(hasXPath("/employees/employee/first-name", containsString("Ja"))); }
XPath also accepts an alternate way of running the equalTo matcher:
@Test public void givenUrl_whenValidatesXmlUsingXpath2_thenCorrect() { post("/employees").then().assertThat() .body(hasXPath("/employees/employee/first-name[text()='Jane']")); }
9. Using Groovy
Since Rest-assured uses Groovy under the hood, we actually have the opportunity to use raw Groovy syntax to create more powerful test cases. This is where the framework really comes to life.
9.1. Groovy’s Collection API
If Groovy is new to you, read on, otherwise you can skip to the section Validate JSON with Groovy.
We will take a quick look at some basic Groovy concepts with a simple examples to equip us with just what we need.
9.2. The findAll method
In this example, we will just pay attention to methods, closures and the it implicit variable. Let us first create a Groovy collection of words:
def words = ['ant', 'buffalo', 'cat', 'dinosaur']
Let’s now create another collection out of the above with words with lengths that exceed four letters:
def wordsWithSizeGreaterThanFour = words.findAll { it.length() > 4 }
Here, findAll is a method applied to the collection with a closure applied to the method. The method defines what logic to apply to the collection and the closure gives the method a predicate to customize the logic.
We are telling Groovy to loop through the collection and find all words whose length is greater than four and return the result into a new collection.
9.3. The it variable
The implicit variable it holds the current word in the loop. The new collection wordsWithSizeGreaterThanFour will contain the words buffalo and dinosaur.
['buffalo', 'dinosaur']
Apart from findAll, there are other Groovy methods.
9.4. The collect iterator
Finally, there is collect, it calls the closure on each item in the collection and returns a new collection with the results of each. Let’s create a new collection out of the sizes of each item in the words collection:
def sizes = words.collect{it.length()}
The result:
[3,7,3,8]
We use sum, as the name suggests to add up all elements in the collection. We can sum up the items in the sizes collection like so:
def charCount = sizes.sum()
and the result will be 21, the character count of all the items in the words collection.
9.5. The max/min operators
The max/min operators are intuitively named to find the maximum or minimum number in a collection :
def maximum = sizes.max()
The result should be obvious, 8.
9.6. The find iterator
We use find to search for only one collection value matching the closure predicate.
def greaterThanSeven=sizes.find{it>7}
The result, 8, the first occurrence of the collection item that meets the predicate.
10. Validate JSON with Groovy
If we have a service at http://localhost:8080/odds, that returns a list of odds of our favorite football matches, like this:
{ "odds": [{ "price": 1.30, "status": 0, "ck": 12.2, "name": "1" }, { "price": 5.25, "status": 1, "ck": 13.1, "name": "X" }, { "price": 2.70, "status": 0, "ck": 12.2, "name": "0" }, { "price": 1.20, "status": 2, "ck": 13.1, "name": "2" }] }
and if we want to verify that the odds with a status greater than 1 have prices 1.20 and 5.25, then we do this:
@Test public void givenUrl_whenVerifiesOddPricesAccuratelyByStatus_thenCorrect() { get("/odds").then().body("odds.findAll { it.status > 0 }.price", hasItems(5.25f, 1.20f)); }
What is happening here is this; we use Groovy syntax to load the JSON array under the key odds. Since it has more than one item, we obtain a Groovy collection. We then invoke the findAll method on this collection.
The closure predicate tells Groovy to create another collection with JSON objects where status is greater than zero.
We end our path with price which tells groovy to create another list of only prices of the odds in our previous list of JSON objects. We then apply the hasItems Hamcrest matcher to this list.
11. Validate XML with Groovy
Let’s assume we have a service at http://localhost:8080/teachers, that returns a list of teachers by their id, department and subjects taught as below:
<teachers> <teacher department="science" id=309> <subject>math</subject> <subject>physics</subject> </teacher> <teacher department="arts" id=310> <subject>political education</subject> <subject>english</subject> </teacher> </teachers>
Now we can verify that the science teacher returned in the response teaches both math and physics:
@Test public void givenUrl_whenVerifiesScienceTeacherFromXml_thenCorrect() { get("/teachers").then().body( "teachers.teacher.find { it.@department == 'science' }.subject", hasItems("math", "physics")); }
We have used the XML path teachers.teacher to get a list of teachers by the XML attribute, department. We then call the find method on this list.
Our closure predicate to find ensures we end up with only teachers from the science department. Our XML path terminates at the subject tag.
Since there is more than one subject, we will get a list which we validate with the hasItems Hamcrest matcher.
11. Conclusion
In this tutorial, we have explored the REST-assured framework and looked at its most important features which we can use to test our RESTful services and validate their responses.
The full implementation of all these examples and code snippets can be found in the REST-assured github project.