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 case | Request | Response |
Get single user | /api/users/2 | 200 response JSON data containing email, etc. |
Get failure | /api/users/23 | 404 response |
Get list | /api/users?page=1 | 200 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 case | Request | Response |
Delete request | /api/users/2 | 204 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 case | Request | Response |
Put request | /api/users/2{ "name": "value", "job": "value" } | 200 responseupdatedAt timestamp |
Patch request | /api/users/2{ "name": "value", "job": "value" } | 200 responseupdatedAt 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 case | Request | Response |
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 responsetoken string |
Login failure | /api/login{ "email": "value" } | 400 responseerror 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.