REST Assured & Groovy GPath – Accessing Values in JSON Response

NEW Posts – Generating Spark and Pdf Extent Report for “Rest Assured with Maven plugin” and “Rest Assured and Cucumber with Maven plugin”

Introduction

This article attempts to shed some light on the various ways of accessing JSON element values from a REST API response using GPath. REST Assured is coded in Groovy and GPath is the path expression language that comes with it. For a detailed explanation of GPath, one can refer to this.

  • JSON element value
  • JSON collection value
  • Groovy closure collection operations
  • Deserialization of JSON to Java object

Though this article only looks at JSON response, the same techniques can be used for XML response.

Project setup

The source code for the article can be found here.

The REST Assured and JUnit dependencies need to be added to the POM. The Jackson or similar artifact is required if JSON is being deserialized. Hamcrest is automatically downloaded with the REST Assured artifact. A sample POM can be found here.

The JSON data is accessed from the publicly available AQI REST API. A token is required for access, which can be easily obtained by registering with an email id.

A sample code to access the API directly is available here.

JSON File Data

The data is live and dynamic, so a JSON file with saved data is used as the source to make the assertion constant. The static JSON data is stored in a file with the following path – src/test/resources/airquality/stations.json.

The data consists of the location details of all available stations between the cities of Berlin and Munich.

The class io.restassured.path.json.JsonPath is used to access the values. The JsonPath object can be initialized with the below code.

JsonPath jsonPath = new JsonPath(new File("src/test/resources/airquality/stations.json"));
{
    "status": "ok",
    "data": [
        {
            "lat": 52.420277,
            "lon": 12.552222,
            "uid": 6180,
            "aqi": "15",
            "station": {
                "name": "Brandenburg a.d.Havel, Germany",
                "time": "2021-11-21T10:00:00+02:00"
            }
        },
        ......        
    ]
}        

AQI REST API Access

The API which generates the above JSON can be accessed in real time by using the below code. Replace the ‘<TOKEN>’ with the credentials obtained by registering. The API documentation can be found here.

given().queryParam("token", "<TOKEN>").queryParam("latlng", "52.520008,13.404954,48.137154,11.576124").when()
       .get("https://api.waqi.info/map/bounds/").then().statusCode(200).log().body();

An example of this code in action can be found here.

JSON Element Value

Q – Verify the status text?

The ‘status‘ element is at the top level of the JSON structure. So just passing the string ‘status’ to the getString() method of JsonPath class does the trick. [Test Method]

assertThat(jsonPath.getString("status"), equalTo("ok"));

When used with the live API, the ‘status’ string is moved inside the body() method and a Hamcrest matcher is added.

given().queryParam("token", "<TOKEN>").queryParam("latlng", "52.520008,13.404954,48.137154,11.576124").when()
       .get("https://api.waqi.info/map/bounds/").then().statusCode(200).body("status", equalTo("ok"));

Q – Verify the uid of the first location?

This can be done in two ways. The ‘data‘ element value is a collection of location objects. The ‘data[0]‘ string gets the first location object and then ‘.uid‘ retrieves the nested value. Alternatively one can use ‘data.uid[0]‘, in this case ‘data.uid’ will return a collection of all uid’s and then add the 0 index to get the first object’s uid value. [Test Method]

assertThat(jsonPath.getInt("data[0].uid"), equalTo(6180));

Similarly to use with the live API. This will most probably fail as the station list sequence is data dynamic.

given().queryParam("token", "<TOKEN>").queryParam("latlng", "52.520008,13.404954,48.137154,11.576124").when()
       .get("https://api.waqi.info/map/bounds/").then().statusCode(200).body("data.uid[0]", equalTo("6180"));

Q – Verify the name of the first station?

In this case the station is also a JSON object which contains the nested name element. [Test Method]

assertThat(jsonPath.getString("data[0].station.name"), equalTo("Brandenburg a.d.Havel, Germany"));
...then().statusCode(200).body("data[0].station.name"), equalTo("Brandenburg a.d.Havel, Germany"));

JSON Collection Value

Q – Verify the aqi readings contain certain values?

The aqi values will need to be collected and for that one can use the getList() method. [Test Method]

assertThat(jsonPath.getList("data.aqi"), hasItems("14", "15", "16"));

There is no difference in the syntax of the selector passed to the body() method when a single value or a collection is expected. Thus one needs to be mindful of the JSON structure.

...then().statusCode(200).body("data.aqi"), hasItems("14", "15", "16"));

Q – Verify the station names contain certain values?

This is similar to the above solution except for the nested station names. [Test Method]

assertThat(jsonPath.getList("data.station.name"), hasItems("Berlin, Germany", "Brandenburg a.d.Havel, Germany", "Prebuz, Karlovarsky, Czech Republic", "Kolpingplatz, Ecke Ringstraße 23 (Parkplatz Sonderschule), Austria"));
...then().body("data.station.name"), hasItems("Berlin, Germany", "Brandenburg a.d.Havel, Germany", "Prebuz, Karlovarsky, Czech Republic", "Kolpingplatz, Ecke Ringstraße 23 (Parkplatz Sonderschule), Austria"));

Collection Operations with Groovy Closures

Groovy collection API’s can be applied to the JsonPath result in case a collection is returned. Some of the methods that can be used are find, findAll, collect, sum, max, min. The find and findAll methods take a closure predicate which can be used for filtering the collection. The collect method take a closure which is applied to each element in the collection.

Q – Verify location uid between certain values

The “it” is an implicit variable for each element in the collection returned by the “data.uid” path. The closure makes use of the “it” variable to select elements with value between the two constraints. [Test Method]

assertThat(jsonPath.getList("data.uid.findAll {it > 6000 && it < 6050}"), containsInAnyOrder(6039, 6041, 6046, 6047, 6048));
...then().body("data.uid.findAll {it > 6000 && it < 6050}"), containsInAnyOrder(6039,6041,6046,6047,6048));

The same result can also be achieved by using multiple findAll methods in a chain. [Test Method]

assertThat(jsonPath.getList("data.uid.findAll {it > 6000}. findAll{ it < 6050}"), containsInAnyOrder(6039, 6041, 6046, 6047, 6048));

The same logic applies for the find method which returns the first matching value.

Q – Verify the station names with aqi less than 10

The “aqi” value needs to be converted from String to int for comparison. This will return all the locations and then the “station.name” path will return the station names. [Test Method]

assertThat(jsonPath.getList("data.findAll {Integer.parseInt(it.aqi) < 10}.station.name"),
 containsInAnyOrder("Johanneskirchen, München, Germany", "Podewilsstraße, Landshut, Germany", "Marktler Straße, Burghausen, Germany"));
...then().body("data.findAll {Integer.parseInt(it.aqi) < 10}.station.name"), containsInAnyOrder("Johanneskirchen, München, Germany", "Podewilsstraße, Landshut, Germany", "Marktler Straße, Burghausen, Germany"));

Q – Verify the station AQI value less than 10

This is a round about way of showing the collect method in action. The String to int conversion is performed inside the collect method and then a findAll method is chained to filter the values. [Test Method]

assertThat(jsonPath.getList("data.aqi.collect {Integer.parseInt(it)}. findAll {it < 10}"), containsInAnyOrder(4, 8, 8));
...then().body("data.aqi.collect {Integer.parseInt(it)}. findAll {it < 10}"), containsInAnyOrder(4, 8, 8));

Q – Verify the maximum aqi value

The collect method is used to convert the String to int and then the max method is called. [Test Method]

assertThat(jsonPath.getInt("data.aqi.collect {Integer.parseInt(it)}. max()"), equalTo(68));
...then().body("data.aqi.collect {Integer.parseInt(it)}. max()"), equalTo(68));

Q – Verify the minimum aqi value

The collect method is used to convert the String to int and then the min method is called. [Test Method]

assertThat(jsonPath.getInt("data.aqi.collect {Integer.parseInt(it)}. min()"), equalTo(4));
...then().body("data.aqi.collect {Integer.parseInt(it)}. min()"), equalTo(4));

Deserialization to Java object

Another way of testing the API JSON response is to deserialize whole or part of the content into Java object. The POJO chain will have to be created. The deserialization will require the Jackson or similar dependency to be included in the POM. [Test Method]

List<Location> locations = jsonPath.getList("data", Location.class);

Location locObj = Location.builder().lat(52.194168f).lon(12.561389f).uid(9312).aqi(16)
.station(Station.builder().name("Lütte (Belzig) +, Germany").time("2021-11-21T10:00:00+02:00").build()).build();

assertThat(locations.get(1), equalTo(locObj));

REST Assured also supports deserialization to a generic type using the io.restassured.mapper.TypeRef class. Refer here for more details.

References

Leave a Reply

Your email address will not be published. Required fields are marked *