Learn to perform automated API testing with REST-assured. We will walk through all important concepts with easy-to-follow examples and code snippets.
1. Introduction
REST-assured is an excellent tool for writing automated tests in BDD style development, although it can be used with other styles. It helps in testing a real or mock REST API in a declarative manner, inspired by other dynamic languages such as Ruby and Groovy.
REST-assured has excellent support for testing HTTP methods and uses methods with names of HTTP verbs, such as get() and post() etc. The general syntax for writing a test is:
given()
...
...
.when()
...
...
.then()
...
...;
As a best practice, always add the following static import statements in the test class so we can use all methods directly. These will include almost all the necessary methods for sending the requests and verifying the response with Hamcrest matchers.
import static io.restassured.RestAssured.*;
import static io.restassured.matcher.RestAssuredMatchers.*;
import static org.hamcrest.Matchers.*;
2. Setting Up with Maven
REST-assured depends on the following modules to work with:
- rest-assured and rest-assured-common: consist of core classes and interfaces.
- json-path: makes it easy to parse JSON documents. It uses Groovy’s GPath syntax.
- json-schema-validator: helps in validating the JSON response structure if we want to.
- xml-path: makes it easy to parse XML documents.
- groovy: if groovy is not present in the classpath, already.
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured-common</artifactId>
<version>5.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-path</artifactId>
<version>5.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>xml-path</artifactId>
<version>5.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>3.0.12</version>
</dependency>
If we are using Java 9+ then we can include all required dependencies with single inclusion using rest-assured-all.
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured-all</artifactId>
<version>5.1.1</version>
<scope>test</scope>
</dependency>
3. Testing with REST-assured
Let us learn the basics of testing APIs using examples.
3.1. Setting Default Host, Port and API Root
Although, we can specify the complete API URL, including hostname and port, in each test method but it may not be appropriate for clean code. REST-assured supports baseURI, port and basePath static variables from class RestAssured to configure common URI parts in a single location.
For example, we are building our examples using the public APIs available at reqres. It’s all APIs start with URL https://reqres.in/api/ so we can define the base URI and API path as follows:
@BeforeAll
public static void setup() {
baseURI = "https://reqres.in/";
basePath = "/api";
}
3.2. GET APIs
The GET API details are as follows:
HTTP GET /users/2
{
"data": {
"id": 2,
"email": "janet.weaver@reqres.in",
"first_name": "Janet",
"last_name": "Weaver"
...
...
}
}
Use get()
method to invoke the API with path parameters. To verify the response data, we can use the inbuilt methods such as equalsTo(), hasItems(), is() etc.
given()
.pathParam("id", 2)
.when()
.get("/users/{id}")
.then()
.statusCode(equalTo(200))
.body("data.id", equalTo(2))
.body("data.email", equalTo("janet.weaver@reqres.in"));
3.3. POST APIs
The demo POST API, we are using the following request.
HTTP POST /users
{
"name": "lokesh",
"email": "admin@howtodoinjava.com"
}
To send a POST request with the body, we can send use body()
method. To verify the response, we can use the methods as discussed in the previous section.
@Test
public void createUserWithCustomObject_thenSuccess() throws JSONException {
given()
.body(new User("lokesh", "admin@howtodoinjava.com"))
.header(new Header("x-custom-header", "value"))
.contentType("application/json")
.when()
.post("/users")
.then()
.statusCode(201)
.body("id", notNullValue())
.body("name", equalTo("lokesh"))
.body("email", equalTo("admin@howtodoinjava.com"));
}
3.4. PUT APIs
For the demo PUT API, we are using the following request. We are updating the email address of employee whose id is 2
.
HTTP PUT /users/2
{
"name": "lokesh",
"email": "admin@howtodoinjava.com"
}
Sending a PUT request is similar to POST request.
@Test
public void updateUser_thenSuccess() {
given()
.body(new User("john", "john@howtodoinjava.com"))
.contentType("application/json")
.when()
.put("/users/2")
.then()
.statusCode(200)
.body("name", equalTo("john"))
.body("email", equalTo("john@howtodoinjava.com"));
}
3.5. DELETE APIs
The DELETE API is straightforward and down not return any response. It returns response code 204 when the entity is deleted.
HTTP DELETE /users/2
Use delete()
method to send HTTP DELETE requests.
@Test
public void deleteUser_thenSuccess() {
when()
.delete("/users/2")
.then()
.statusCode(204)
.body(isEmptyOrNullString());
}
4. Building Request Headers and Body
4.1. Request Headers
For sending request headers, use RequestSpecification.header()
method.
given()
...
.header(new Header("x-custom-header", "value"))
.contentType("application/json")
...
4.2. Request Body
For sending requests with a request body, we have two options:
- Either, using JSONObject to build a custom JSON payload. Internally, REST-assured uses JSON.simple to parse the object to JSON payload.
JSONObject requestParams = new JSONObject();
requestParams.put("name", "lokesh");
requestParams.put("email", "admin@howtodoinjava.com");
given()
.body(requestParams.toString())
...
.when()
- Or, using a custom object that produces the required JSON payload.
given()
.body(new User("lokesh", "admin@howtodoinjava.com"))
...
.when()
5. Verifying Response Status and Body
Use any of the Hamcrest matchers to match the parts of the response including response status code, response headers or response body.
given()
...
.when()
...
.then()
.statusCode(201)
.header("x-response-header", equalTo("value"))
.body("id", notNullValue())
.body("name", equalTo("lokesh"))
.body("email", equalTo("admin@howtodoinjava.com"))
6. Extracting Response data
Suppose we want to reuse the data from the response of an API, we can extract it using the extract()
method.
String id = given()
.body(requestParams.toString())
.contentType("application/json")
.when()
.post("/users")
.then()
.statusCode(equalTo(201))
.extract().body().path("id");
7. Logging Response Data
Use the ValidatableResponseLogSpec returned from body()
method to log using the Hamcrest log()
method.
given()
...
.when()
...
.then()
...
.log().body();
8. Measuring Response Time and Testing Timeouts
Using the time()
method, we can measure the API response time for verifying performance SLAs. It returns the time in milliseconds. To get the time in a specific timeunit, pass the TimeUnit as method argument.
long timeInMs = get("...").time();
long timeInSeconds = get("...").timeIn(TimeUnit.SECONDS);
We can use the time() method with hamcrest matches to test the timeouts.
when().
get("...").
then().
time(lessThan(1000L), TimeUnit.SECONDS);
As a best practice, we should test the timeouts when JVM is warmed up. Measuring timeouts in a single test may not give the correct result as it will include a lot of other initializations at the JVM level.
9. Conclusion
In this tutorial, we learned using REST-assured to write automated tests for real API testing. This is an excellent solution for BDD development where we can start writing the API mocks and tests for it, and later we write the actual API implementations.
Happy Learning !!