Testing a REST API may seem complicated, but proper testing can save hours (if not days) of tedious debugging and make the implementation of new features go smoothly. The right approach can detect potential problems early on, minimizing the risk of later complications. In the following article, we’ll take a look at what elements are worth considering to make sure your API testing is effective.
1. What to test API with?
The most popular tool is Postman. Postman allows you to send HTTP requests to selected API endpoints and then analyze the responses returned by the server. It can easily test whether the API works as intended and simulate various user scenarios. The tool also has the ability to organize queries into collections, which makes them easier to manage, and supports team collaboration by allowing tests to be shared with other team members.
If one would like to automate API tests, the Rest Assured library may be a good choice. It is a popular tool designed to automate API tests in Java, which allows you to create scripts that test the performance of RESTful interfaces. With Rest Assured, you can write readable and flexible API tests that integrate with popular testing frameworks such as JUnit and TestNG. The tool offers user-friendly DSL syntax, making it easy to define HTTP requests and verify server responses.
2. How to test API?
When testing an API, it is easy to fall into the trap of checking only positive scenarios. What is meant here is a situation where we focus only on cases where all parameters and input data are correct, and server responses meet expectations. In such tests, the API works according to the documentation, which gives a sense of stability, but ignores important aspects, such as the application’s behavior with incorrect or incomplete data, unusual situations or unauthorized access attempts. The lack of negative scenarios can lead to an underestimation of potential problems that users may encounter in real-world conditions.
What scenarios should we assume then? Below is a list of the types of tests I use when verifying each API:
As mentioned above, positive scenarios
For example – we are to test the addition of a new user. To do this, we send a POST request to the endpoint /api/users. The content of the request will look like the following:
{
"name": "Jan Kowalski",
"email": "jkowalski@example.com"
}The data is correct (after all, what we care about here is the success of the test). We should get a 201 response code and returned content with confirmation of user data, for example:
{
"id": 103,
"name": "Jan Kowalski",
"email": "jkowalski@example.com"
}Operation successfully completed!
Negative scenarios
But as I wrote above, clients don’t just pay us to test scenarios that don’t assume problems that may arise along the way. Example -this time let’s send this request without the new user’s email:
{
"name": "Jan Kowalski"
}Obviously, the above request is incorrect – it does not contain one required field, which is the user’s e-mail address. Thus, we expect an error here. We should receive an HTTP code indicating an incorrect request (400 Bad Request), and the user should not be created . In addition, the server should return an error message, such as the one below:
{
"error": "Email is required"
}Such a test confirms that the API validates the input correctly and does not allow creating a user without key information.
Destructive scenarios
In our set of tests we should still include the so-called destructive scenario, a kind of negative test that checks how the API handles extremely incorrect or malicious data. Example – let’s send a POST request to our endpoint /api/users:
{
"name": "Jan Kowalski",
"email": "<script>alert('Hacked');</script>"
}In the above example, the “email” field contains a malicious code instead of a valid email address. The API should respond with a status code of 400 (Bad Request) or another error code, without executing the malicious code. We should also receive a response with an error message.
Edge scenarios
This type of testing (also called boundary testing) checks how the API reacts to boundary values for input data. An example would be testing the minimum and maximum length of a text field.
In our example of creating a new user, we can assume that there is a maximum number of characters allowed for first and last name – let it be 50 characters. In this case, we should check the cases of :
- What happens when you send input with a field
nameof exactly 50 characters - what happens at 51 characters and 49 characters
- attempt to send a request with an empty field (0 characters)
- a very large number, such as 1000 characters
- special characters e.g. @#$%^&* to see if the API accepts or rejects the
namefield containing such characters - start or end with whitespace characters (e.g. “ John Smith “) to check if the backend correctly accepts / trims spaces
Integration scenarios
What could an integration scenario be? Let’s imagine that we create a new user using the endpoint /api/users (positive scenario). However, let’s take it a step further – suppose our scenario involves not only creating a user, but also checking the user’s permissions using the endpoint /api/permissions.
That is – we need to create a new user and get information with his ID:
{
"id": 103,
"name": "Jan Kowalski",
"email": "jkowalski@example.com"
}Then we send a request POST /api/permissions using the ID of the created user for this. We want to assign it a role such as “user”:
{
"userId": 103,
"role": "user"
}We get the answer:
{
"userId": 103,
"role": "user",
"status": "assigned"
}Such a test verifies that the various components of the application work properly together.
Authorization and authentication
To begin with, an explanation of how the two terms differ. Authentication involves verifying the identity of a user who is trying to access a resource. Authentication verifies that the authenticated user has the proper permissions to a specific resource. It’s worth looking at both when testing an API to protect access to resources.
How to test authentication? Make sure that correct login credentials, such as a valid token, allow access to resources. Also test what happens if you provide incorrect authentication data, such as an invalid login, password, JWT token or expired token. The API should return appropriate errors, such as a 401 Unauthorized code.
In cases where the API supports different roles (e.g., standard user and administrator), make sure that each user has access only to the resources to which he or she should have permissions – that’s how you check authorization.
3. What exactly should be checked?
When I test a REST API, I focus on the following aspects of the API in question:
Response codes
To begin with the obvious thing, that is, the correctness of response codes. We should verify that after sending the request to the server, the returned HTTP status code is as expected. This status indicates whether the request was processed successfully or whether there was a problem.
Here are examples:
- We send a request of type
GETto/products/123. In response we should get a status of 200 (OK) if this product exists. The code 200 means that the request was successful - On the other hand, if you send a GET request under the ID of a non-existent product, such as under /products/9999, the server should return a 404 code, suggesting that the requested resource is not available
- When resources are protected and require authentication, we can test. For a GET /user/account request sent without a valid authorization token, the API should respond with a 401 code, suggesting a lack of access privileges
In general, the rule is – we send a request and check whether the server response code is correct. We remember not to check only positive test cases. For example, it is worth checking what happens if the request is incorrectly worded or if the user does not have the right permissions.
Response structures
The structure and content of the response (JSON schema) refers to whether the API response is structured correctly and contains all expected fields with the correct data types. Why is this tested? To make sure that the API responses follow the established schema, which helps avoid client-side errors that can occur when the response is not as expected.
For example – suppose the API returns user data in JSON format. The expected response scheme might look like this:
{
"id": 103,
"name": "Jan Kowalski",
"email": "jkowalski@example.com",
"role": "user
}In the JSON schema test, we check that the response contains all the required fields (id, name, email, role) and that they have the correct data types (id as an integer, the rest of the fields as character strings). Missing any of these fields or wrong data type (e.g. id as string instead of number) should result in a test error.
Response times
These tests measure how long it takes the API to respond to a request from the time it is sent to the time it receives a full response. In other words, we measure the time it takes to get a response from the server – this can be any value, usually measured in milliseconds, such as 250 ms.
The results are compared with predetermined expectations, which may, for example, specify that the response time should not exceed 200 ms.

Headers
HTTP headers specify additional information about the request or response, such as content type, credentials, caching information and more. Inadequate or missing headers can lead to erroneous data processing, security problems, and unnecessary load on the server or client.
The most commonly tested headlines are:
- Content-Type – specifies the type of data in the response, such as.
application/json - Authorization – contains credentials (e.g., JWT tokens) that confirm a user’s identity or authorization to access resources