REST API Testing Using REST Assured
By ski11up.com
Every software application, web or mobile has APIs built into it to serve the user with a dynamic response based on business logic. For example, retrieving the list of users logged in between the given start and end date, the API will form the query at run time and request the backend (database) server and push it to the front end for the user view. Another example is an e-commerce application doing various dynamic operations between frontend and backend like searching product-specific models etc.
APIs are critical to the success/failure of any software application, they need to be fast enough in returning the response to the end-user as well as the correctness of the response data makes it useful for the user. APIs also need to be able to authenticate the user so that unauthorized access is restricted, they also need to be able to manage optimal load in case of heavy usage of software application, for example, many people are accessing the application at one point of time, placing the order, etc.
While the performance of a given API can be tested using JMeter, we will be using Rest Assured for functional testing.
Rest Assured
Rest Assured is an open-source REST-API testing framework which has inbuild support for assertions JavaHamcrest.
Rest Assured is the most popular API testing framework with BDD
Behaviour Driven Development approach.
Lets understand how we can use rest-assured
for testing http://api.weatherstack.com API.
weatherstack.com gives free access to the weather API for 100 requests per day. |
Weather API
We need to validate if the schema is correct, based on the given input the API retrieves the right set of values or not.
Below is the sample response body for a location as New York
, based on this we will derive tests.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"request": {
"type": "City",
"query": "New York, United States of America",
"language": "en",
"unit": "m"
},
"location": {
"name": "New York",
"country": "United States of America",
"region": "New York",
"lat": "40.714",
"lon": "-74.006",
"timezone_id": "America\/New_York",
"localtime": "2020-08-21 18:45",
"localtime_epoch": 1598035500,
"utc_offset": "-4.0"
},
"current": {
"observation_time": "10:45 PM",
"temperature": 29,
"weather_code": 113,
"weather_icons":
["https:\/\/assets.weatherstack.com\
/images\/wsymbols01_png_64\/wsymbol_0001_sunny.png"],
"weather_descriptions": ["Sunny"],
"wind_speed": 9,
"wind_degree": 220,
"wind_dir": "SW",
"pressure": 1013,
"precip": 0,
"humidity": 53,
"cloudcover": 0,
"feelslike": 31,
"uv_index": 7,
"visibility": 16,
"is_day": "yes"
}
}
Response Body has:
-
Fixed Structure
-
Three sections
request
,location
andcurrent
The first test is obvious, verifying the response status and content type and then the response body if it is correct. Other tests will be validating the values in the response body.
In addition to the above, we will see how we can pass the query parameter(s) using HashMap
.
For unpredictable response values use the range in between assertion. For example, you cannot guess temperature but you can guess the temperature as high/low. |
Response Status, Content-Type
The below test shows how we can use rest-assured
for testing the Response Status Code
and Response Content-Type
. We need to pass the query parameter to get the response from the API.
For accessing data via REST APIs
we use access methods like POST
, GET
, PUT
, DELETE
etc. Rest Assured
provides us handle to perform these actions on a given API. Let’s take a closer look at the below code and understand each line one by one.
@Test
@DisplayName("Request Status Code, Content Type.")
public void requestStatusContentTypeTest() {
given() (1)
.queryParam("access_key", "d637155faf02fb9d9c05d8bd0ba6438b") (2)
.queryParam("query", "New York") (2)
.when() (3)
.post("http://api.weatherstack.com/current") (4)
.then() (5)
.statusCode(200) (6)
.contentType("application/json"); (7)
}
1 | given() is the starting point of all rest-assured tests |
2 | we need to pass the query parameter to get the required response, here we are passing access_key as a first request parameter, look at .queryParam this is used for passing the values to the query parameter in the API, we can pass n-number of parameters based on the API requirement by including another .queryParam , here we are passing location as query parameter |
3 | .when this is BDD way of testing which start with given() some condition and .when() , you can call it the way rest-assured is designed to be used by test engineers |
4 | .post POST Method for the given API, we have passed the URL under test |
5 | .then following the BDD philosophy given, when, then |
6 | this is the way we can do assertions without using any third-party assertion API, statusCode is inbuilt |
7 | same as above contentType is inbuild so we can call it directly |
Response Schema Validation
As a part of API testing it is good to check if the response schema is correct as often times developer may have changed the schema or it got changed as a part of other changes.
Rest Assured
provides us a way to validate the schema, this can be achieved by providing the input schema JSON
to match with.
Save the below schema file in src/test/resources
, JUnit5
has this classpath and can read all files from this folder without specifying any location.
You can create the schema
of any API response
with the help of JSON Schema Utility.
{
"type": "object",
"title": "The root schema",
"description": "The root schema comprises the entire JSON document.",
"required": [
"request",
"location",
"current"
],
"properties": {
"request": {
...
"required": [
"type",
"query",
"language",
"unit"
],
},
"location": {
...
"required": [
"name",
"country",
"region",
"lat",
"lon",
"timezone_id",
"localtime",
"localtime_epoch",
"utc_offset"
],
},
"current": {
...
"required": [
"observation_time",
"temperature",
"weather_code",
"weather_icons",
"weather_descriptions",
"wind_speed",
"wind_degree",
"wind_dir",
"pressure",
"precip",
"humidity",
"cloudcover",
"feelslike",
"uv_index",
"visibility",
"is_day"
],
}
}
}
Take a look at the above schema
definition, it has the response
objects
and its parameter
name, this is what rest-assured
will match with the actual response.
io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath
does the required match.
@Test
@DisplayName("Schema Validation.")
public void schemaValidationTest() {
Map<String, String> param = new HashMap<>();
param.put("access_key", "d637155faf02fb9d9c05d8bd0ba6438b");
param.put("query", "New York");
Response response =
given()
.when()
.post(
"http://api.weatherstack.com/current"
+ "?access_key={access_key"
+ "}&query={query}",
param);
String responseBody = response.getBody().asString();
assertThat(responseBody,
matchesJsonSchemaInClasspath("schema.json")); (1)
}
1 | validation of response schema againts schema.json |
Response Body
Hamcrest
does the assertion with rest-assured
BDD
tests.
Request Object
@Test
@DisplayName("Request.")
public void requestTest() {
/*
* TODO: Assertions for:
* 1. request.localtime
* 2. current.localtime_epoch
*/
given()
.queryParam("access_key", "d637155faf02fb9d9c05d8bd0ba6438b")
.queryParam("query", "New York")
.when()
.post("http://api.weatherstack.com/current")
.then()
.statusCode(200)
.assertThat()
.body(
"request.type", (1)
equalTo("City"), (2)
"request.query",
equalTo("New York, United States of America"),
"request.language",
equalTo("en"),
"request.unit",
equalTo("m"));
}
1 | request.type has the request object and type is the object parameter |
2 | equalTo does the assertion on the response body values |
Note that request.query
has the request
object and the parameter is query
.
Location Object
@Test
@DisplayName("Location.")
public void locationTest() {
String currentDateTime = getCurrentDateTime("America/New_York");
given()
.queryParam("access_key", "d637155faf02fb9d9c05d8bd0ba6438b")
.queryParam("query", "New York")
.when()
.post("http://api.weatherstack.com/current")
.then()
.statusCode(200)
.assertThat()
.body(
"location.name",
equalTo("New York"),
"location.country", (1)
equalTo("United States of America"),
"location.region",
equalTo("New York"),
"location.lat",
equalTo("40.714"),
"location.lon",
equalTo("-74.006"),
"location.timezone_id",
equalTo(currentDateTime), (2)
"location.utc_offset",
equalTo("-4.0"));
}
1 | location.country has the location object and country is the object parameter |
2 | currentDateTime variable will be matched with the location.timezone_id |
Current Object
@Test
@DisplayName("Current.")
public void currentTest() {
/*
* TODO: Assertions for:
* 1. current.observation_time
* 2. current.temperature
* 3. current.weather_code
* 4. current.weather_descriptions
* 5. current.weather_icons
*/
String observationTime = getTime("America/New_York");
given()
.queryParam("access_key", "d637155faf02fb9d9c05d8bd0ba6438b")
.queryParam("query", "New York")
.when()
.post("http://api.weatherstack.com/current")
.then()
.statusCode(200)
.assertThat()
.body(
"current.wind_speed",
greaterThanOrEqualTo(0), (1)
"current.wind_degree",
greaterThanOrEqualTo(0),
"current.wind_dir",
oneOf("EW", "EN", "ES", "WE",
"WN", "WS", "WE", "NE", "NW", "NS", "SE", "SW", "SN"), (2)
"current.pressure",
greaterThan(0), (3)
"current.precip",
greaterThanOrEqualTo(0),
"current.humidity",
greaterThan(0),
"current.cloudcover",
greaterThanOrEqualTo(0),
"current.feelslike",
greaterThan(0),
"current.uv_index",
greaterThan(0),
"current.visibility",
greaterThan(0),
"current.is_day",
oneOf("yes", "no")); (4)
}
1 | greaterThanOrEqualTo can be used as we don’t know what will be the value API will return but it cannot be -ve |
2 | oneOf the given values will be in the response body |
3 | it is obvious that current.pressure is alway greaterThan ZERO |
You can use Hamcrest
to do different kinds of input validations.
Passing Query Param as HashMap
The below test shows the usage of HashMap
in a post
request, this makes code more readable and cleaner. Key {access_key}
and {query}
are the query parameter and at run time there values will be passed to the request.
@Test
@DisplayName("Post Param as HashMap.")
public void apiCallAsMapTest() {
Map<String, String> param = new HashMap<>(); (1)
param.put("access_key", "d637155faf02fb9d9c05d8bd0ba6438b"); (2)
param.put("query", "New York"); (3)
given()
.when()
.post(
"http://api.weatherstack.com/current" +
"?access_key={access_key}&query={query}", param) (4)
.then()
.statusCode(200)
.contentType("application/json")
.body("request.type", equalTo("City"));
}
1 | Defines the Query Parameter HashMap |
2 | Adds access_key to the Query Parameter HashMap |
3 | Adds location to the Query Parameter HashMap |
4 | Passing HashMap to the post request |
Project Setup
We are using maven
for project dependency, import maven
dependency for rest-assured
and json-schema-validator
.
Maven Dependency
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest.assured.version}</version>
<scope>test</scope>
</dependency>
For schema
validation import below dependency.
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>${json.schema.validator}</version>
<scope>test</scope>
</dependency>
Add below version
under maven properties
.
<!--RestAssured-->
<rest.assured.version>4.3.1</rest.assured.version>
<json.schema.validator>4.3.1</json.schema.validator>
Summary
We have covered API testing using rest-assured
, verified request schema
, response body
and in the last section we have used maven
as our project dependency tool for project setup.
Source Code
You can either git fork
, git clone
or download this repository rest-assured-api-tests. Make sure that you clone the main repository as it has parent pom.xml
.