Users Online
· Members Online: 0
· Total Members: 188
· Newest Member: meenachowdary055
Forum Threads
Latest Articles
Articles Hierarchy
Rest Assured Tutorial for REST API Automation Testing
In this chapter, we will implement the Deserialization of JSON Responses we receive. It helps us to read the body of the response. Subsequently, in this process of deserialization, we will Convert JSON Response Body to Java Objects. It is also known as Object Representation of JSON responses.
Additionally, to learn more about the deserialization of JSON response, we will recommend you to go through the Deserialize JSON Response.
We will follow the below steps to achieve the deserialization of our responses in the framework. We followed them in the previous chapter of Convert JSON Request Body to Java Object as well.
- Create POJO classes for each of our Response Objects:
- Book
- Books
- Token
- User Account
- Replace the Response bodies in Step files with POJO class objects
- Run the tests
Convert JSON Response Body to Java Object
Firstly, we need to convert the JSON Response into a POJO class for our response object. So, let us learn to create a POJO class out of a JSON Response. Under deserialization, we will study our JSON body parameters and create a POJO class of it. Let's begin with a straightforward request example for Get A Book request:
Create POJO class Book
As a part of this request, we are sending the ISBN parameter to get details of a specific book.
Consequently, the response body from the Swagger Bookstore API, as highlighted in the above image is:
{
"books": [
{
"isbn": "9781449325862",
"title": "Git Pocket Guide",
"subTitle": "A Working Introduction",
"author": "Richard E. Silverman",
"published": "2013-08-02T00:00:00",
"publisher": "O'Reilly Media",
"pages": 234,
"description": "This pocket guide is the perfect on-the-job companion to Git, the distributed version control system. It provides a compact, readable introduction to Git for new users, as well as a reference to common commands and procedures for those of you with Git experience.",
"website": "https://chimera.labs.oreilly.com/books/1230000000561/index.html"
}
}
As a part of the next step, we will create a POJO class for our response object. Additionally, you may use the same online utility, as we discussed in the previous chapter, to convert the response to a POJO class.
To create a POJO class of it, follow the below steps:
1. Firstly, Right-click on the model Package under the apiEngine package. After that, select New>>Class. Name it as Book. Moreover, we will capture the book response under this class.
Book.class
package apiEngine.model;
public class Book {
public String isbn;
public String title;
public String subTitle;
public String author;
public String published;
public String publisher;
public int pages;
public String description;
public String website;
}
Code Explanation
This Book class will contain all the fields we got in the response, such as title, isbn, author, no.of pages, etc.
Create a POJO class for Books Response Object:
The response body from the Bookstore API, as highlighted in the below image is:
{
"userID": "9b5f49ab-eea9-45f4-9d66-bcf56a531b85",
"userName": "TOOLSQA-Test",
"books": [
{
"isbn": "9781449325862",
"title": "Git Pocket Guide",
"subTitle": "A Working Introduction",
"author": "Richard E. Silverman",
"published": "2013-08-02T00:00:00",
"publisher": "O'Reilly Media",
"pages": 234,
"description": "This pocket guide is the perfect on-the-job companion to Git, the distributed version control system. It provides a compact, readable introduction to Git for new users, as well as a reference to common commands and procedures for those of you with Git experience.",
"website": "https://chimera.labs.oreilly.com/books/1230000000561/index.html"
}
]
}
Note: As a part of the response body, we got the book details of the book we added for the user as well as other user details such as userName and userID.
To create a POJO class of it, follow the below steps:
-
Firstly, in this model Package, Right-click on the model and select New >> Package. Name it as responses. Additionally, we will capture all the response classes under this package.
-
Secondly, Right-click on the above-created responses Package and select New >> Class. Name it as Books.
Books.class
package apiEngine.model.responses;
import java.util.List;
import apiEngine.model.Book;
public class Books {
public List<Book> books;
}
Code Explanation We created the class Books to hold a list of the type Book which we receive in the JSON response in GET Books Request.
Create a POJO class for Token Object:
We have understood the process of creating a POJO class by now, as shown in the above examples. So, in the next APIs, I will directly add the POJO of respective API's. Consequently, the response body for Generate Token API from our Bookstore API is:
{
"token": "string",
"expires": "2020-03-14T13:42:15.716Z",
"status": "string",
"result": "string"
}
To create a POJO class of it, Right-click on the above-created responses Package and select New >> Class. After that, name it as Token.
package apiEngine.model.responses;
public class Token {
public String token;
public String expires;
public String status;
public String result;
}
Code Explanation
We created the class Token that will contain all the fields we got in the response field, namely, token, expiry, status, and a result which we receive in the JSON response of GenerateToken.
Create a POJO class for User Account Object:
Similarly, for User Account API, the response body is:
{
"userID": "9b5f49ab-eea9-45f4-9d66-bcf56a531b85",
"userName": "TOOLSQA-Test",
"books": []
}
To create a POJO class of the response body, Right-click on the above-created responses Package. After that, select New >> Class. Name it as UserAccount.
package apiEngine.model.responses;
import java.util.List;
import apiEngine.model.Book;
public class UserAccount {
public String userID;
public String userName;
public List<Book> books;
}
Code Explanation
We created the class UserAccount to hold the information associated with the user account, such as books associated with the user, the userID, and userName.
As our next step of project implementation, we shall modify our Steps class.
Replace the Response bodies in Step files with POJO class objects
Now it is the time to make use of the POJO classes created above in the test code. Let's have a look at the one of the old cucumber step below in the Step Definition:
@Given("^I am an authorized user$")
public void iAmAnAuthorizedUser() {
AuthorizationRequest credentials = new AuthorizationRequest("TOOLSQA-Test","Test@@123");
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
response = request.body(credentials).post("/Account/v1/GenerateToken");
String jsonString = response.asString();
//We will deserialize the Response body into Token Response
token = JsonPath.from(jsonString).get("token");
}
we update it likewise:
@Given("^I am an authorized user$")
public void iAmAnAuthorizedUser() {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
AuthorizationRequest authRequest = new AuthorizationRequest("TOOLSQA-Test", "Test@@123");
response = request.body(authRequest).post("/Account/v1/GenerateToken");
// Deserializing the Response body into tokenResponse
tokenResponse = response.getBody().as(Token.class);
}
Note The import statement: import io.restassured.path.json.JsonPath; is no longer needed as we have deserialized our response to Token class.
Code Explanation: We deserialized the response body into the Token class in this step above. This response body gets saved in the "tokenResponse" variable. The saved variable will be used further in a request along with the token.
We put together these code snippets for our Steps file:
package stepDefinitions;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import apiEngine.model.Book;
import apiEngine.model.requests.AddBooksRequest;
import apiEngine.model.requests.AuthorizationRequest;
import apiEngine.model.requests.ISBN;
import apiEngine.model.requests.RemoveBookRequest;
import apiEngine.model.response.Books;
import apiEngine.model.response.Token;
import apiEngine.model.response.UserAccount;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import io.restassured.RestAssured;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
public class Steps {
private static final String USER_ID = "9b5f49ab-eea9-45f4-9d66-bcf56a531b85";
private static final String BASE_URL = "https://bookstore.toolsqa.com";
private static Response response;
private static Token tokenResponse;
private static Book book;
@Given("I am an authorized user")
public void iAmAnAuthorizedUser() {
AuthorizationRequest credentials = new AuthorizationRequest("TOOLSQA-Test","Test@@123");
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
response = request.body(credentials).post("/Account/v1/GenerateToken");
// Deserializing the Response body into tokenResponse
tokenResponse = response.getBody().as(Token.class);
}
@Given("A list of books are available")
public void listOfBooksAreAvailable() {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
response = request.get("/BookStore/v1/Books");
// Deserializing the Response body into Books class
Books books = response.getBody().as(Books.class);
book = books.books.get(0);
}
@When("I add a book to my reading list")
public void addBookInList() {
ISBN isbn = new ISBN(book.isbn);
AddBooksRequest addBooksRequest = new AddBooksRequest(USER_ID, isbn);
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + tokenResponse.token)
.header("Content-Type", "application/json");
response = request.body(addBooksRequest).post("/BookStore/v1/Books");
}
@Then("The book is added")
public void bookIsAdded() {
Assert.assertEquals(201, response.getStatusCode());
UserAccount userAccount = response.getBody().as(UserAccount.class);
Assert.assertEquals(USER_ID, userAccount.userID);
Assert.assertEquals(book.isbn, userAccount.books.get(0).isbn);
}
@When("I remove a book from my reading list")
public void removeBookFromList() {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
RemoveBookRequest removeBookRequest = new RemoveBookRequest(USER_ID, book.isbn);
request.header("Authorization", "Bearer " + tokenResponse.token)
.header("Content-Type", "application/json");
response = request.body(removeBookRequest).delete("/BookStore/v1/Book");
}
@Then("The book is removed")
public void bookIsRemoved() {
Assert.assertEquals(204, response.getStatusCode());
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + tokenResponse.token)
.header("Content-Type", "application/json");
response = request.get("/Account/v1/User/" + USER_ID);
Assert.assertEquals(200, response.getStatusCode());
UserAccount userAccount = response.getBody().as(UserAccount.class);
Assert.assertEquals(0, userAccount.books.size());
}
}
Run the Cucumber Test
Run the Tests as JUnit
We are all set now to run the updated Cucumber test. First, Right -Click on TestRunner class and Click Run As >> JUnit Test. Cucumber runs the script in the same way as Selenium WebDriver. Consequently, the result will display in the JUnit tab of the console.
Run the Tests from Cucumber Feature
To run the tests as a Cucumber Feature, right-click on the End2End_Test.feature file. After that, select the Run As>>Cucumber Feature.
Our tests passed with the changes we made for the conversion of the JSON Response Body to Java Object. Please try to implement it in your framework, as explained above, and share your valuable feedback.
After creating the above POJO classes, the project folder structure will look likewise:
Subsequently, we will attempt the separation of Test Layer with API Services in our next chapter.
We learned to convert JSON Request Body as well as JSON Response Body to Java Object in the previous chapters. We used Serialization and Deserialization concepts to achieve this. It led to our tests becoming more maintainable and less error-prone. Now in this chapter, we will work on the Separation of Test Layer with API Services and take a step further to make our tests cleaner and maintainable.
If you look at the Steps class, which is our test layer, you would see that we have a lot of logic that isn't actually related to testing. It concerns communicating with the server with a given test request and getting a response. Refer to the screenshot below:
In actual projects, we have a large number of APIs and tests implemented. They all contain the same logic of communicating with the server in every test, like:
- Buiding RequestSpecification object
- Adding headers
- Making calls to the server
This leads to a lot of code duplication in every test file. Additionally, if tomorrow there are some changes required in a particular endpoint, we will need to make changes in several places. It is not advisable because it makes our framework less maintainable.
Additionally, the test layer needs to focus only on the test data (parameters) sent in the request and receive responses from the APIs. It should not be focused on the heavy logic of the internals of API implemented. So, as software testers, we are only interested in the request and response obtained for these requests. Moreover, the test data sent in the request generally pass from the feature file in the Cucumber framework. This test data can be parameters to form a request body, request header or request resources.
As we have seen in our first chapter Understanding API Documentation, endpoints indicate ways to access resources from the server. Thus, we will essentially combine all the logic of endpoints from our Steps file and move it to a common class. The class will contain methods that take the required request parameters and send back a response received from the server. The endpoints should always get the request body from the test layer. In this article, we are going to cover:-
- Separation of Test Layer with API Services
- Create API Services for the Test
- Create API Services
- Add methods of API Services
- Call API Services in the Step Definitions(test layer)
- Run the tests
Separation of Test Layer with API Services
We abstract the logic of communication with the server into a separate class. It will make our Steps class cleaner. Subsequently, as we progress to write more steps into Step-Definition files in the framework, for different scenarios, we can reuse this logic of communicating with the server.
Consider the example to generate a token request of our Book Store API: /Account/v1/GenerateToken
For writing tests to test the generate token, our tests should focus the response obtained for the request body passed. In addition to this, they should not focus on internal implementation for the API. In a project with a large number of APIs, it is better to separate the Endpoints from Steps file.
Moreover, if we send correct credentials, we get a success response as a result. Refer below screenshot:
But, if we send invalid credentials, we will get an error response.
Thus, as testers, the focus is on the parameters sent in the body of the request and response we get. It is as same as what we are doing at the Book Store API in Swagger Tool above. We do not have a clue how swagger is making use of HTTP and how it is communicating with the server. The same way the test layer should be agnostic, the fact of how the service layer is handling the request. Additionally, a test layer should only focus on parameters passed to the request, whether it is query parameter, header parameters, or a body parameter, etc.
Let's see how to separate the Service layer from the Test layer.
Create API Services for the Test
Now we are going to create API Services, which will be consumed by the test layer, and this way, the test layer will be clean and efficiently focus only on test parameters.
- Firstly, create API Services
- Secondly, add methods of API Services
- Thirdly, call API Services in the Step Definitions(test layer)
- Fourthly, run the tests
Create API Services
- Firstly, Right-click on the apiEngine package and select Class. Additionally, name it as Endpoints. It would be our API Services class.
package apiEngine;
public class Endpoints {
}
We are moving all our endpoints to the Endpoints class. It is rational to move the BASE URL as well to the Endpoints class from Steps file. Moreover, it will help us to refer to the BASE URL when sending requests to our endpoints.
Thus, the updated Endpoints class will look likewise:
package apiEngine;
public class Endpoints {
private static final String BASE_URL = "https://bookstore.toolsqa.com";
}
Add methods of API Services
Firstly, we will extract the way to authenticate the user from Steps into Endpoints class. Additionally, we shall pass the object of *AuthorizationRequest *to the method.
public static Response authenticateUser(AuthorizationRequest authRequest) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
Response response = request.body(authRequest).post("/Account/v1/GenerateToken");
return response;
}
Code Explanation:
As you can see, we have created a method authenticateUser. In this method, we pass user credentials through AuthorizationRequest object, authRequest in the Request. Moreover, we are passing the BASE_URL and the headers as part of the request as well.
Similar to the above authenticateUser() method, we will create methods for :
- getBooks()
- addBook()
- removeBook()
- getUserAccount()
Putting all the methods together for the Endpoints class:
Endpoints.java class
package apiEngine;
import apiEngine.model.requests.AddBooksRequest;
import apiEngine.model.requests.AuthorizationRequest;
import apiEngine.model.requests.RemoveBookRequest;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
public class EndPoints {
private static final String BASE_URL = "https://bookstore.toolsqa.com";
public static Response authenticateUser(AuthorizationRequest authRequest) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
Response response = request.body(authRequest).post("/Account/v1/GenerateToken");
return response;
}
public static Response getBooks() {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
Response response = request.get("/BookStore/v1/Books");
return response;
}
public static Response addBook(AddBooksRequest addBooksRequest, String token) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
Response response = request.body(addBooksRequest).post("/BookStore/v1/Books");
return response;
}
public static Response removeBook(RemoveBookRequest removeBookRequest, String token) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
Response response = request.body(removeBookRequest).delete("/BookStore/v1/Book");
return response;
}
public static Response getUserAccount(String userId, String token) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
Response response = request.get("/Account/v1/User/" + userId);
return response;
}
}
We now move to our next step. In the next step, we will replace the endpoints in the Steps file with our newly created methods.
Talk with API Services in the Step Definitions
We have abstracted the logic of communication with the server into an Endpoint class. It will now help us make our Steps class cleaner as we progress into replacing the endpoints with the associated methods. In addition to this, we will be reusing this logic of communicating with the server.
Thus, the first part of the Steps class, with changes in the declared variables will be:
public class Steps {
private static final String USER_ID = "9b5f49ab-eea9-45f4-9d66-bcf56a531b85";
private static Response response;
private static Token tokenResponse;
private static Book book;
For the step definition:
@Given("I am an authorized user")
is updated as:
@Given("I am an authorized user")
public void iAmAnAuthorizedUser() {
AuthorizationRequest authRequest = new AuthorizationRequest("TOOLSQA-Test", "Test@@123");
response = EndPoints.authenticateUser(authRequest);
tokenResponse = response.getBody().as(Token.class);
}
We will likewise modify the rest of our step definitions. Moreover, we put all these changes for the Step Definitions together into the Steps file.
The updated Steps file will look likewise:
package stepDefinitions;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import apiEngine.Endpoints;
import apiEngine.model.Book;
import apiEngine.model.requests.AddBooksRequest;
import apiEngine.model.requests.AuthorizationRequest;
import apiEngine.model.requests.ISBN;
import apiEngine.model.requests.RemoveBookRequest;
import apiEngine.model.response.Books;
import apiEngine.model.response.Token;
import apiEngine.model.response.UserAccount;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import io.restassured.response.Response;
public class Steps {
private static final String USER_ID = "9b5f49ab-eea9-45f4-9d66-bcf56a531b85";
private static Response response;
private static Token tokenResponse;
private static Book book;
@Given("I am an authorized user")
public void iAmAnAuthorizedUser() {
AuthorizationRequest authRequest = new AuthorizationRequest("TOOLSQA-Test", "Test@@123");
response = Endpoints.authenticateUser(authRequest);
tokenResponse = response.getBody().as(Token.class);
}
@Given("A list of books are available")
public void listOfBooksAreAvailable() {
response = Endpoints.getBooks();
Books books = response.getBody().as(Books.class);
book = books.books.get(0);
}
@When("I add a book to my reading list")
public void addBookInList() {
ISBN isbn = new ISBN(book.isbn);
AddBooksRequest addBooksRequest = new AddBooksRequest(USER_ID, isbn);
response = Endpoints.addBook(addBooksRequest, tokenResponse.token);
}
@Then("The book is added")
public void bookIsAdded() {
Assert.assertEquals(201, response.getStatusCode());
UserAccount userAccount = response.getBody().as(UserAccount.class);
Assert.assertEquals(USER_ID, userAccount.userID);
Assert.assertEquals(book.isbn, userAccount.books.get(0).isbn);
}
@When("I remove a book from my reading list")
public void removeBookFromList() {
RemoveBookRequest removeBookRequest = new RemoveBookRequest(USER_ID, book.isbn);
response = Endpoints.removeBook(removeBookRequest, tokenResponse.token);
}
@Then("The book is removed")
public void bookIsRemoved() {
Assert.assertEquals(204, response.getStatusCode());
response = Endpoints.getUserAccount(USER_ID, tokenResponse.token);
Assert.assertEquals(200, response.getStatusCode());
UserAccount userAccount = response.getBody().as(UserAccount.class);
Assert.assertEquals(0, userAccount.books.size());
}
}
Run the Test
Run the Tests as JUnit
We are all set now to run the updated Cucumber test. Firstly, Right -Click on TestRunner class and Click Run As >> JUnit Test. Cucumber will run the script in the same fashion as it runs in Selenium WebDriver. Finally, the result will display in the left-hand side project explorer window in the JUnit tab.
Run the Tests from Cucumber Feature
To run the tests as a Cucumber Feature, right-click on the End2End_Test.feature file. After that, select the Run As>>Cucumber Feature.
Moreover, our updated project folder structure of the framework will look likewise:
In the next chapter, we will introduce the Implementation of Routes in Endpoints in our API automation framework. That would help us to make the framework maintainable and we won't have to make changes everywhere when the route changes except for Routes class. Additionally, please implement the Endpoints class as discussed above, and share your valuable feedback with us.