So far, we have converted our Rest Assured E2E API tests into Cucumber BDD Style Tests. Subsequently, our next step would Convert JSON to JAVA Object using Serialization. We have covered Serialization and Deserialization tutorial in Java. It would be highly appreciated if you revisit the Serialization and Deserialization chapter to understand well what's going around overall in our next stage of framework development.
Convert JSON to JAVA Object
The request body we send right now is in this format:
request.body("{ \"userName\":\"" + USERNAME + "\", \"password\":\"" + PASSWORD + "\"}")
.post("/Account/v1/GenerateToken");
We are sending the body request in a raw JSON string format. It is cumbersome to maintain and error-prone to send the request body in this format. Right now, we are dealing with just two parameters. But, there is a possibility that in actual body requests, we could have to deal with more number of parameters.
Additionally, it is advisable to send the username and password in the request body as an object. To achieve this we need to convert JSON to JAVA Object. But, the network does not understand Java objects. So, we would need to serialize the objects into String before sending the request.
We can do this using many serialization-deserialization libraries available. But, Rest Assured has this functionality in-built. It enables us to directly send objects in Rest Assured requests while the Library takes care of Serialization internally. If you dive deeper to understand the implementation of RequestSpecification, you will see the below code snippet which clearly shows how Rest Assured takes care of Serialization.
So we will now understand the process of converting a JSON request into a Java object in the next section.
Create a POJO class for a Request Body
We are focussing on creating a POJO class for our request object. So, let us learn to create a POJO class out of a JSON. Let's begin with one simple request example from our Request Body:
Consider the API endpoint: /Account/v1/GenerateToken
In the Request body for this API, we are sending the username and password as the request body.
As seen in the above Swagger bookstore API document, the request JSON body parameters we pass in the request body is:
{
"userName": "TOOLSQA-Test",
"password": "Test@@123"
}
Moreover, you can manually create a POJO class having all listed fields of the JSON body. Also, there are various utilities available online for free using which we can convert any JSON structure to a Java POJO class.
How to create a Java POJO Class for a JSON Request Body using Online Utility?
Here we will see How to Convert a JSON String into Java Object. So, let's take the help of one of the websites to help us convert the JSON to a Java POJO class.
Please follow these steps
- Firstly, navigate to the website: http://www.jsonschema2pojo.org/
- Secondly, enter the JSON body in the left text box.
- Thirdly, enter the package name and class name in the right-side panel.
- Finally, enter other required selection details as per the image below.
Click on the preview button highlighted in the above image.
The following image is displayed:
Explanation
Thus, with our inputs of a JSON with username and password as fields, we have created a POJO. Additionally, we will use this POJO to send into the request body of our API /Account/v1/GenerateToken.
Steps to Update the JSON String with POJO classes
We will follow these steps to implement serialization in our framework:
- Firstly, create POJO classes for each of our Request Objects:
- Token Request
- Add Books Request
- ISBN
- Remove Books Request
- Secondly, replace the Request bodies in Step files with POJO class objects.
- Finally, run the tests.
POJO class for Authorization Token Request:
As shown in the above image from our Swagger bookstore API documentation for the Generate Token API, the request body is:
{
"userName": "TOOLSQA-Test",
"password": "Test@@123"
}
To create a POJO class of it, please follow the below steps:
-
Firstly, Right-click on the src/test/java and select New >> Package. After that, create a New Package file and name it as apiEngine. Further inside, the apiEngine Package creates a new Package with the name as the model. Moreover, inside this model Package, create a Package with the name requests. We will capture all the requests classes under this package.
-
Secondly, create a New Class file under it and name it as AuthorizationRequest, by right click on the above-created Package and select New >> Class.
AuthorizationRequest.class
package apiEngine.model.requests;
public class AuthorizationRequest {
public String username;
public String password;
public AuthorizationRequest(String username, String password) {
this.username = username;
this.password = password;
}
}
Code Explanation
The Bookstore URL requires the client to send the username and password as we studied in the API Documentation tutorial for Authorization Token. We have defined the class AuthorizationRequest for this purpose. We will create an object of AuthorizationRequest with a constructor with the parameters username and password.
Similarly, we will create classes for Add Books Request, Remove Books Request, and ISBN.
POJO class for Add Books Request:
As we saw in the Swagger bookstore API documentation for the Add Books API, the request body is:
{
"userId": "string",
"collectionOfIsbns": [
{
"isbn": "string"
}
]
}
To create a POJO class of the JSON request body, Right-click on the above-created request Package and select New >> Class. Additionally, name it as AddBooksRequest.
AddBooksRequest.class
package apiEngine.model.requests;
import java.util.ArrayList;
import java.util.List;
public class AddBooksRequest {
public String userId;
public List<ISBN> collectionOfIsbns;
//As of now this is for adding a single book, later we will add another constructor.
//That will take a collection of ISBN to add multiple books
public AddBooksRequest(String userId, ISBN isbn){
this.userId = userId;
collectionOfIsbns = new ArrayList<ISBN>();
collectionOfIsbns.add(isbn);
}
}
Code Explanation
The AddBooksRequest class will be responsible for providing us an object that adds a book into the user account. While we are at it, we would need the userId and the unique identifier of the book, ISBN. Thus, the userID and isbn pass as a parameter into the AddBooksRequest class object.
POJO class for ISBN:
Right-click on the above-created request Package. After that, select New >> Class. Name it as ISBN.
package apiEngine.model.requests;
public class ISBN {
public String isbn;
public ISBN(String isbn) {
this.isbn = isbn;
}
}
Code Explanation
We created the ISBN class to use in the AddBooksRequest class for storing a collection of the type ISBN.
POJO class for Remove Book Request:
As we saw in the Bookstore API for Remove Books Endpoint, the request body is:
{
"isbn": "string",
"userId": "string"
}
To create a POJO class of the JSON request body, Right-click on the above-created request Package and select New >> Class. Additionally, name it as RemoveBookRequest.
package apiEngine.model.requests;
public class RemoveBookRequest {
public String isbn;
public String userId;
public RemoveBookRequest(String userId, String isbn) {
this.userId = userId;
this.isbn = isbn;
}
}
Code Explanation
With the help of RemoveBooksRequest class, we will create an object by passing the parameters userId and isbn. Moreover, the request body uses this object.
Replace the Request Bodies in Step files with POJO class objects
Lets just first start with the first step of the test that is "Given("^I am an authorized user$")"
We made the following change in its Step Definition:
@Given("^I am an authorized user$")
public void iAmAnAuthorizedUser() {
AuthorizationRequest authRequest = new AuthorizationRequest("TOOLSQA-Test","Test@@123");
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
response = request.body(authRequest).post("/Account/v1/GenerateToken");
String jsonString = response.asString();
token = JsonPath.from(jsonString).get("token");
}
Code Explanation
We have created an object, authRequest of the class AuthorizationRequest. In this object, we are passing username and password. Additionally, this authRequest object will pass in the body of the request.
In a similar vein, we will make changes for the following Step Definitions to use Java objects that we created using the POJOs we defined. Internally, the rest assured library will take care of serializing this object into JSON string before sending the request over the network. Thus, we do not have to worry about serializing the request objects.
@When("^I add a book to my reading list$")
@When("I remove a book from my reading list")
Also, note that for the remaining three Step Definitions, we would not be making any changes for now.
@Given("A list of books are available")
@Then("^The book is added$")
@Then("^The book is removed$")
We put together all these code changes for Steps file
package stepDefinitions;
import java.util.List;
import java.util.Map;
import apiEngine.model.requests.AddBooksRequest;
import apiEngine.model.requests.AuthorizationRequest;
import apiEngine.model.requests.ISBN;
import apiEngine.model.requests.RemoveBookRequest;
import org.junit.Assert;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.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 String token;
private static Response response;
private static String jsonString;
private static String bookId;
@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();
token = JsonPath.from(jsonString).get("token");
}
@Given("^A list of books are available$")
public void listOfBooksAreAvailable() {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
response = request.get("/BookStore/v1/Books");
jsonString = response.asString();
List<Map<String, String>> books = JsonPath.from(jsonString).get("books");
bookId = books.get(0).get("isbn");
}
@When("^I add a book to my reading list$")
public void addBookInList() {
AddBooksRequest addBooksRequest = new AddBooksRequest(USER_ID, new ISBN(bookId));
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + 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());
}
@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, bookId);
request.header("Authorization", "Bearer " + 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 " + token)
.header("Content-Type", "application/json");
response = request.get("/Account/v1/User/" + USER_ID);
Assert.assertEquals(200, response.getStatusCode());
jsonString = response.asString();
List<Map<String, String>> booksOfUser = JsonPath.from(jsonString).get("books");
Assert.assertEquals(0, booksOfUser.size());
}
}
Note: We added an import statement for JSONpath, import io.restassured.path.json.JsonPath; It will help us to traverse through the specific parts of the JSON. Moreover, you can read more in the JSONPath article.
Now, if we try to run our tests, using TestRunner after making all the changes in the Steps file, our tests will fail.
The Runtime-Exception, we will come across is:
java.lang.IllegalStateException: Cannot serialize object because no JSON serializer found in classpath. Please put Jackson (Databind), Gson, Johnzon, or Yasson in the classpath.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:80)
at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:59)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:237)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:249)
at io.restassured.internal.mapping.ObjectMapping.serialize(ObjectMapping.groovy:160)
at io.restassured.internal.mapping.ObjectMapping$serialize.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
at io.restassured.internal.RequestSpecificationImpl.body(RequestSpecificationImpl.groovy:751)
at stepDefinitions.Steps.iAmAnAuthorizedUser(Steps.java:38
We are missing a key element in the whole show. As explained above in the first section, Rest Assured takes care of Serialization internally. We also confirmed it in the code snippet. The Rest Assured library depends on the Jackson (Databind) library, to do this work of Serialization. Since we haven’t yet added this library, we faced the error.
Let us quickly add the dependency in our framework project file pom.xml:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
Note: As of Feb 2019, the latest available dependency for jackson-databind was of version 2.10.2. Please use the latest dependencies when you build your framework.
Our new POM file will look like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ToolsQA</groupId>
<artifactId>APITestingFramework</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm-deps</artifactId>
<version>1.0.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
Run the Cucumber Test
Run the Tests as JUnit
Now we are all set to run the updated Cucumber test. Right -Click on TestRunner class, and after that, click Run As >> JUnit Test. 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.
As you can see in the screenshot attached above, our tests have passed with the changes. We have Convert JSON to JAVA Object using Serialization in this chapter. Please try to implement it in your framework, as explained above. Below is the screenshot of the current project explorer.
Subsequently, we will visit the Convert JSON Response Body to POJO in our next chapter.