API testing with REST Assured

As mentioned in Getting started with API’s, tools like Postman are useful for hands-on testing of API’s, but automated API testing is better performed in code and there are several frameworks available to support this, with Rest Assured being one of the most widely used.

A test endpoint

Obviously to test an API, we need one to send requests to and ideally this should support as many features of a real-world API as possible. API’s for real services often require authentication, and publicly available API’s typically do not allow state to be changed (they’re usually read only sources of information). There’s a list of API’s maintained at https://apilist.fun/ suitable for experimentation with.

For the purposes of this article, we will use https://reqres.in/, which while not actually supporting the creation, deletion and modification of data, does mimic those operations sufficiently to allow us to experiment for test purposes.

Defining test cases

REST API’s commonly support CRUD operations (Create/Retrieve/Update/Delete) and the well documented test API endpoint supports the HTTP verbs GET, POST, PUT, PATCH and DELETE along with sample calls and expected responses, so that would be a good place to start.

GET – retrieve data

Easily testable, so let’s try some cases that return different responses.

Test caseRequestResponse
Get single user/api/users/2200 response
JSON data containing email, etc.
Get failure/api/users/23404 response
Get list/api/users?page=1200 response
JSON data including an array of users

DELETE – fairly obvious what that does

Well, sort of – but it only responds with success and doesn’t actually change the state on the server.

Test caseRequestResponse
Delete request/api/users/2204 response

PUT & PATCH – update data

These HTTP verbs also return successful responses but don’t change the server state. We’ll test both verbs.

Test caseRequestResponse
Put request/api/users/2
{ "name": "value", "job": "value" }
200 response
updatedAt timestamp
Patch request/api/users/2
{ "name": "value", "job": "value" }
200 response
updatedAt timestamp

POST – create data

This HTTP verb also return various responses but doesn’t change the server state. We’ll cover some of the more interesting responses.

Test caseRequestResponse
Create user/api/users/2
{ "name": "value", "job": "value" }
201 response
Data returned with updatedAt timestamp and id string
Login success/api/login
{ "email": "value", "password": "value" }
200 response
token string
Login failure/api/login
{ "email": "value" }
400 response
error string, “missing password

These requests and responses are fully documented at https://reqres.in/, and can be experimented with using Postman.

Implementing the tests

While it is possible to implement all these tests in a single file, that will soon become difficult to read and maintain, and good practice is to break the tests down into different files grouped logically (just as you would break down manual test cases).

Note that readability and maintainability are the key things here – don’t abstract everything away using as many OOP concepts as possible – if you or someone else will have to maintain and extend the tests you write, make them well documented and well organised.

Rather than have common code duplicated in every file, place that somewhere it can be inherited by anything that requires it (an object orientated programming principle).

The fully implemented tests can be found at https://github.com/ObjectiveTester/AllThingsTesting.com-examples/tree/master/SimpleRestTest, and can be cloned from https://github.com/ObjectiveTester/AllThingsTesting.com-examples.git

The base class

This class holds common code used in all other tests. We’re using REST Assured and need to define the API endpoint for the tests to actually access, so we’ll define that with a default value and allow it to be easily reconfigured via command line arguments

GET test with assertions

The GET test cases are fairly readable, in this test we are retrieving user 2 and checking the name and email are correct (as well as the returned HTTP status code):

@Test
public void testGetSingle() {
    given().when().get("/users/2").then()
            .body("data.first_name", equalTo("Janet"))
            .body("data.last_name", equalTo("Weaver"))
            .body("data.email", equalTo("janet.weaver@reqres.in"))
            .statusCode(200);
}

POST test of data, asserting on the response

The POST tests require data to be sent – this is constructed using using the json.org library and sent via HTTP POST. the response returns a non-deterministic String id, so we assert that part of the returned data matches that sent, and that a non-specific ID string is returned with instanceOf(String.class):

@Test
public void testCreateUser() {
    JSONObject data = new JSONObject();
    data.put("name", "James Kirk");
    data.put("job", "Commanding Officer");

    given().contentType("application/json").body(data.toString())
            .when().post("/users").then()
            .body("name", equalTo("James Kirk"))
            .body("id", instanceOf(String.class))
            .statusCode(201);
}

PUT (or PATCH)

The update tests are similarly structured, the key difference is which HTTP verb is used:

@Test
public void testPutRequest() {
    JSONObject data = new JSONObject();
    data.put("name", "Hikaru Sulu");
    data.put("job", "Helmsman");

    given().contentType("application/json").body(data.toString())
            .when().put("/users/2").then()
            .body("name", equalTo("Hikaru Sulu"))
            .body("updatedAt", instanceOf(String.class))
            .statusCode(200);
}

Running the tests

If you have Maven and a JDK, the tests should be runnable from the command line with:

mvn clean test

If not, you can start them from a Java IDE. Assuming the endpoint is accessible, they should all pass.

Further investigation

To delve further into API testing, try enhancing and extending the sample tests, or implement a set of tests for a different publicly accessible endpoint.