In the previous chapter, we introduced the usage of Generics for the Endpoints class of our application framework. It helped us to handle different response objects we get. In this chapter, we will change the static methods of Endpoints.java class into instance methods and work on Refactoring Request Headers. It is also important to understand the difference between the Static and Instance Method in programming.
By converting the EndPoint class into an Instance class, it will help us to make use of a Single Request Object. It will save us the trouble of adding the request header each time we pass the request. Here are the topics which we are going to cover in this article:-
- Need for Refactoring Request Headers
- Steps for Refactoring Request Headers
- Convert static methods into instance methods
- Update the Steps file
- Run the tests
So let's get started.
Need for Refactoring Request Headers
Consider the methods from the below image of Endpoints.java class:
We are sending this highlighted piece of code - the BASE_URL, the headers in every method, as we call our methods in the Step Definitions. It leads to us creating the RequestSpecification object again and again when it is the same for every step. It would be simpler if we created this once for all the steps.
If one uses the static methods, it causes random failures in the tests as they turn flaky if the tests run in parallel in the future course. Every thread tries to access the available static resource. During this, the thread could come in a situation where another thread is in a position to access the same static resources. Thus, to avoid the test failures during parallel execution.
To achieve this, we will need to initialize the RequestSpecification object in the constructor. Therefore now we will need to make all these methods in Endpoints.java class as non-static.
But what we would achieve from this for the framework? Once the authentication is set in the RequestSpecification object, it is good for all the subsequent requests. It is not required to set it again during the next request.
Let's see how to do this.
Steps for Refactoring Request Headers
To refactor the existing code to create just a single request object, we follow these steps:
- Convert static methods into instance methods
- Update the Steps file
- Run the tests
So let's begin with our first step.
Convert Static Methods into Instance Methods
The changes in the Endpoints.java class would be:
package apiEngine;
import org.apache.http.HttpStatus;
import apiEngine.model.requests.AddBooksRequest;
import apiEngine.model.requests.AuthorizationRequest;
import apiEngine.model.requests.RemoveBookRequest;
import apiEngine.model.responses.Books;
import apiEngine.model.responses.Token;
import apiEngine.model.responses.UserAccount;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
public class EndPoints {
private final RequestSpecification request;
public EndPoints(String baseUrl) {
RestAssured.baseURI = baseUrl;
request = RestAssured.given();
request.header("Content-Type", "application/json");
}
public void authenticateUser(AuthorizationRequest authRequest) {
Response response = request.body(authRequest).post(Route.generateToken());
if (response.statusCode() != HttpStatus.SC_OK)
throw new RuntimeException("Authentication Failed. Content of failed Response: " + response.toString() + " , Status Code : " + response.statusCode());
Token tokenResponse = response.body().jsonPath().getObject("$", Token.class);
request.header("Authorization", "Bearer " + tokenResponse.token);
}
public IRestResponse<Books> getBooks() {
Response response = request.get(Route.books());
return new RestResponse(Books.class, response);
}
public IRestResponse<UserAccount> addBook(AddBooksRequest addBooksRequest) {
Response response = request.body(addBooksRequest).post(Route.books());
return new RestResponse(UserAccount.class, response);
}
public Response removeBook(RemoveBookRequest removeBookRequest) {
return request.body(removeBookRequest).delete(Route.book());
}
public IRestResponse<UserAccount> getUserAccount(String userId) {
Response response = request.get(Route.userAccount(userId));
return new RestResponse(UserAccount.class, response);
}
}
Code Explanation:
As you can see in the above code, we have created a constructor which takes care of initializing the RequestSpecification object with baseURL and Request Headers in the constructor when the Endpoints class initializes in the Steps file.
We also need token to be passed as well in request Header. For that in the method authenticateUser() when we get tokenResponse, we set it in the same instance of request. This header will then be available when we make subsequent calls to the server. Since authenticating the user will always be the first step, we will not need to add the token in the header for every request we make after that.
Update the Steps file for the above change
Our Steps file too would change as per the Endpoints.java class
package stepDefinitions;
import apiEngine.EndPoints;
import apiEngine.IRestResponse;
import apiEngine.model.*;
import apiEngine.model.requests.*;
import apiEngine.model.responses.*;
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.response.Response;
public class Steps {
private final String USER_ID = "9b5f49ab-eea9-45f4-9d66-bcf56a531b85";
private Response response;
private IRestResponse<UserAccount> userAccountResponse;
private Book book;
private final String BaseUrl = "https://bookstore.toolsqa.com";
private EndPoints endPoints;
@Given("^I am an authorized user$")
public void iAmAnAuthorizedUser() {
endPoints = new EndPoints(BaseUrl);
AuthorizationRequest authRequest = new AuthorizationRequest("TOOLSQA-Test", "Test@@123");
endPoints.authenticateUser(authRequest);
}
@Given("^A list of books are available$")
public void listOfBooksAreAvailable() {
IRestResponse<Books> booksResponse = endPoints.getBooks();
book = booksResponse.getBody().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);
userAccountResponse = endPoints.addBook(addBooksRequest);
}
@Then("^The book is added$")
public void bookIsAdded() {
Assert.assertTrue(userAccountResponse.isSuccessful());
Assert.assertEquals(201, userAccountResponse.getStatusCode());
Assert.assertEquals(USER_ID, userAccountResponse.getBody().userID);
Assert.assertEquals(book.isbn, userAccountResponse.getBody().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);
}
@Then("^The book is removed$")
public void bookIsRemoved() {
Assert.assertEquals(204, response.getStatusCode());
userAccountResponse = endPoints.getUserAccount(USER_ID);
Assert.assertEquals(200, userAccountResponse.getStatusCode());
Assert.assertEquals(0, userAccountResponse.getBody().books.size());
}
}
Code Explanation:
We have initialized an Endpoints class object-endpoints and passed the baseURL in the step iAmAnAuthorizedUser() by invoking a constructor of Endpoints class. Also, this same endpoint object is being used by all the step definitions instead of calling static Endpoint methods.
Run the tests
We are all set now to run the updated Cucumber test. Subsequently, we will Right-Click on the TestRunner class. After that, we will Click Run As >> JUnit Test. Consequently, the result will appear 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. Select the Run As>>Cucumber Feature.
Our updated project folder structure of the framework will look likewise:
Our tests passed with the changes we made for the Refactoring of the Request Headers in our framework. We will learn to share Test Context between our Step Definitions so that we can have the step definition file uncluttered and share the Test Context with all the Step Definition files. It will avoid the complexity of adding the auth header repeatedly for each request.