In our previous chapter, we introduced the concept of using an Endpoints class in our framework. As a means to abstract the logic of communication with the server into a separate class and to make our steps cleaner, we introduced this Endpoints class. Subsequently, in this chapter, we are going to take it a step further by introducing the REST Routes concept in our framework. As you all know, we use an API endpoint to carry out a specific task. Moreover, Routes are the paths through which we can access the endpoints. In simpler terms, we call the route as URI, while an endpoint is an action we perform on the URI.
What are REST Routes in API?
As we know, client-server communication is one of the REST (representational state transfer) principles. Additionally, it provides a mapping between the HTTP verbs like GET, PUT, POST, DELETE, etc. with CRUD(create, read, update, and delete) operations. We click on links and navigate through various sites. Moreover, in this process, we are merely making a state transition when we navigate to one site from another. The REST-compliant systems are often referred to as RESTful systems.
In addition to the above, the RESTful routes can be akin to a traditional pattern followed when structuring different routes. It interacts with the server each time an HTTP request happens.
For example in the URL,
http://bookstore.toolsqa.com/BookStore/v1/Books:
BasePath: http://bookstore.toolsqa.com/
Route: /BookStore/v1/Books
Endpoint: There can be multiple tasks we can perform on this route. For example:
GET- http://bookstore.toolsqa.com/BookStore/v1/Books | To fetch a list of books |
POST- http://bookstore.toolsqa.com/BookStore/v1/Books | To add a book associated with the user |
DELETE - http://bookstore.toolsqa.com/BookStore/v1/Books | To remove books associated with the user |
It is how the endpoints looked in our Swagger Bookstore API
Implementation of REST Routes in Framework
We will follow the steps below to implement Routes in our framework:
- Firstly, Create a Route class
- Secondly, Modify the Endpoints class
- Thirdly, Run the tests
Create a Route class
Let us add the Routes we discussed above in the first section to our Route class. We will keep all the routes at a single place, and wherever the routes are required, we will use it from this class.
Moreover, the advantage it gives is that suppose any of the route changes. We won't have to make changes everywhere. In other words, it's just at a single place in the Routes class.
-
Firstly, to create a Route class, Right-click on the apiEngine Package and select New >>Class. Name it as Route.
-
After that, add the following code snippet to it:
package apiEngine;
public class Route {
private static final String BOOKSTORE = "/BookStore";
private static final String ACCOUNT = "/Account";
private static final String VERSION = "/v1";
public static String generateToken(){
return ACCOUNT + VERSION + "/GenerateToken";
}
public static String books(){
return BOOKSTORE + VERSION + "/Books";
}
public static String book(){
return BOOKSTORE + VERSION + "/Book";
}
public static String userAccount(String userId){
return ACCOUNT + VERSION + "/User" + "/" + userId;
}
}
Modify the Endpoints class for REST Routes
We will modify the methods in the endpoints class. Additionally, we will pass the routes from the Routes class instead of writing them to a specific route in the method itself.
For Example:
Our method authenticateUser() is updated from:
public static Response authenticateUser(AuthorizationRequest authRequest) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
//The hardcoded route "/Account/vi/GenerateToken" will be modified to take the route from the Route class
Response response = request.body(authRequest).post("/Account/v1/GenerateToken");
return response;
}
to
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(Route.generateToken());
return response;
}
Explanation:
In this method, the route for generating token is now taken from the Route class instead of passing the route "/Account/v1/GenerateToken".
Similarly, we update the below methods for the Endpoints class:
- getBooks()
- addBook()
- removeBook()
- getUserAccount()
Consequently, our updated Endpoints class will look likewise:
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(Route.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(Route.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(Route.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(Route.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(Route.userAccount(userId));
return response;
}
}
Run the Cucumber Test
Run the Tests as JUnit
We are all set now to run the updated Cucumber test. First, Right -Click on the TestRunner class. Secondly, Click Run As >> JUnit Test. Finally, 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 Rest Routes implementation in our framework. Our updated project folder structure of the framework will look likewise:
Subsequently, in the next chapter, we will implement generics in our framework to handle response objects of various data types. Meanwhile, please try and apply the above changes in your framework, as explained.