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.

Also, to learn about more advanced use-cases of REST-assured, check out our other articles:

Now let’s dive in with a simple example.

2. Simple Example Test

Before we get started, let’s ensure that our 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.3.0</version>
    <scope>test</scope>
</dependency>

To get the latest version, follow this link.
REST-assured takes advantage of the power of Hamcrest matchers to perform its assertions, so we must include that dependency as well:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>2.1</version>
</dependency>

The latest version will always be available at this link.

4. 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));

5. 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));

6. Specifying the Request Method

Typically, we would perform a request by calling a method such as get(), corresponding to the request method we want to use.

In addition, we can also specify the HTTP verb using the request() method:

@Test
public void whenRequestGet_thenOK(){
    when().request("GET", "/users/eugenp").then().statusCode(200);
}

The example above is equivalent to using get() directly.

Similarly, we can send HEAD, CONNECT and OPTIONS requests:

@Test
public void whenRequestHead_thenOK() {
    when().request("HEAD", "/users/eugenp").then().statusCode(200);
}

POST request also follows a similar syntax and we can specify the body by using the with() and body() methods.

Therefore, to create a new Odd by sending a POST request:

@Test
public void whenRequestedPost_thenCreated() {
    with().body(new Odd(5.25f, 1, 13.1f, "X"))
      .when()
      .request("POST", "/odds/new")
      .then()
      .statusCode(201);
}

The Odd object sent as body will automatically be converted to JSON. We can also pass any String that we want to send as our POST body.

7. Default Values Configuration

We can configure a lot of default values for the tests:

@Before
public void setup() {
    RestAssured.baseURI = "https://api.github.com";
    RestAssured.port = 443;
}

Here, we’re setting a base URI and port for our requests. Besides these, we can also configure the base path, root pat, and authentication.

Note: we can also reset to the standard REST-assured defaults by using:

RestAssured.reset();

8. Measure Response Time

Let’s see how we can measure the response time using the time() and timeIn() methods of the Response object:

@Test
public void whenMeasureResponseTime_thenOK() {
    Response response = RestAssured.get("/users/eugenp");
    long timeInMS = response.time();
    long timeInS = response.timeIn(TimeUnit.SECONDS);
    
    assertEquals(timeInS, timeInMS/1000);
}

Note that:

  • time() is used to get response time in milliseconds
  • timeIn() is used to get response time in the specified time unit

8.1. Validate Response Time

We can also validate the response time – in milliseconds – with the help of simple long Matcher:

@Test
public void whenValidateResponseTime_thenSuccess() {
    when().get("/users/eugenp").then().time(lessThan(5000L));
}

If we want to validate the response time in a different time unit, then we’ll use the time() matcher with a second TimeUnit parameter:

@Test
public void whenValidateResponseTimeInSeconds_thenSuccess(){
    when().get("/users/eugenp").then().time(lessThan(5L),TimeUnit.SECONDS);
}

9. XML Response Verification

Not only can it validate a JSON response, itcan 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 shorthand 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"));
}

10. XPath for XML

We can also 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']"));
}

11. Logging Test Details

11.1. Log Request Details

First, let’s see how to log entire request details using log().all():

@Test
public void whenLogRequest_thenOK() {
    given().log().all()
      .when().get("/users/eugenp")
      .then().statusCode(200);
}

This will log something like this:

Request method:    GET
Request URI:    https://api.github.com:443/users/eugenp
Proxy:            <none>
Request params:    <none>
Query params:    <none>
Form params:    <none>
Path params:    <none>
Multiparts:        <none>
Headers:        Accept=*/*
Cookies:        <none>
Body:            <none>

To log only specific parts of the request, we have the log() method in combination with params(), body(), headers(), cookies(), method(), path() eg log.().params().

Note that other libraries or filters used may alter what’s actually sent to the server, so this should only be used to log the initial request specification.

11.2. Log Response Details

Similarly, we can log the response details.

In the following example we’re logging the response body only:

@Test
public void whenLogResponse_thenOK() {
    when().get("/repos/eugenp/tutorials")
      .then().log().body().statusCode(200);
}

Sample output:

{
    "id": 9754983,
    "name": "tutorials",
    "full_name": "eugenp/tutorials",
    "private": false,
    "html_url": "https://github.com/eugenp/tutorials",
    "description": "The \"REST With Spring\" Course: ",
    "fork": false,
    "size": 72371,
    "license": {
        "key": "mit",
        "name": "MIT License",
        "spdx_id": "MIT",
        "url": "https://api.github.com/licenses/mit"
    },
...
}

11.3. Log Response if Condition Occurred

We also have the option of logging the response only if an error occurred or the status code matches a given value:

@Test
public void whenLogResponseIfErrorOccurred_thenSuccess() {
 
    when().get("/users/eugenp")
      .then().log().ifError();
    when().get("/users/eugenp")
      .then().log().ifStatusCodeIsEqualTo(500);
    when().get("/users/eugenp")
      .then().log().ifStatusCodeMatches(greaterThan(200));
}

11.4. Log if Validation Failed

We can also log both request and response only if our validation failed:

@Test
public void whenLogOnlyIfValidationFailed_thenSuccess() {
    when().get("/users/eugenp")
      .then().log().ifValidationFails().statusCode(200);

    given().log().ifValidationFails()
      .when().get("/users/eugenp")
      .then().statusCode(200);
}

In this example, we want to validate that the status code is 200. Only if this fails, the request and response will be logged.

12. 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.


« 上一篇: Spring Security表达式