Rest Assured Tutorial for REST API Automation Testing
This is a series of Rest Assured Tutorial which is one of the most used library for REST API Automation Testing. Rest-Assured is a Java-based library that is used to test RESTful Web Services. This library behaves like a headless Client to access REST web services. We can create highly customize-able HTTP Requests to send to the Restful server. This enables us to test a wide variety of Request combinations and in turn test different combinations of core business logic.
Rest-Assured library also provides the ability to validate the HTTP Responses received from the server. For e.g. we can verify the Status code, Status message, Headers and even the Body of the response. This makes Rest-Assured a very flexible library that can be used for testing.
This is a complete series of Rest Assured Tutorial for REST API Automation Testing where the following topics will be covered:
For any web application, the API services work as the building block. Therefore it becomes very important for you, as a tester, to ensure that the web services are bug-free and facilitate the exchange of information securely. In this article series, we will cover the automation of Rest API using RestAssured. But before jumping onto it, let us look at some of the basics to strengthen our knowledge of APIs. One of the basic concepts to start with is by understanding the client server architecture. In this article, we will understand below points-
Client server architecture in a simple sense can be stated as a consumer-producer model where the client acts as the consumer i.e. the service requestor and the server is the producer, i.e. the service provider. Let us see how they are described in the computing sense.
A client in computing is a system or a program that connects with a remote system or software to fetch information. The client makes a request to the server and is responded with information. There may be three types of clients - thick, thin or hybrid client.
A server is a system or a computer program that acts as the data provider. It may provide data through LAN(Local Area Network) or WAN(Wide Area Network) using the internet. The functionalities provided by the server are called services. These services are provided as a response to the request made by the clients. Some of the common servers are-
These clients and servers do not necessarily be at the same location. They could either be located in different locations or may reside as different processes on the same computer. They are connected via the Web and interact via the HTTP protocol which will be discussed later in this article. There may be multiple clients requesting a server and alternatively, a client can request from multiple servers.
The below diagram visualizes a typical client-server model-
As shown in the diagram, a single server may serve the requests of multiple clients. Similarly, a client may be requesting data from different servers. For example, consider an example of Google. Here, Google acts as the server and the users sitting at different places act as the clients.
Another example that we come across in our everyday life is the Online Banking Portal in which the browser that we use to open a portal acts as the client while the database and the software for banking act as the server. This propagates resource sharing across multiple users and thereby results in cost-efficiency along with time saving.
In the next section let us see how the client-server model works and how does this interaction takes place.
As already discussed above, the client server model acts like a consumer-server relationship. But how does it work? Let us see some of the flow of information in a client-server architecture through a series of steps as discussed below-
The above diagram shows the communication process between the client and the server. The client sends an HTTP request, for which the server sends an HTTP response. Let us consider an example to understand more about it.
Assume that you need to go shopping from your home to a shop across the road. You may travel on a bicycle and on reaching can order the salesman the goods you need. Now if the salesman finds the goods, he will bring them to you else he would let you know about their unavailability.
Now, you may link the above example with the navigation to a website, the street between your home and the shop is the internet connection. The mode of transport that you chose to travel is the TCP/IP, defining the communication protocol for the data to travel through the internet. The address of the shop is the DNS(Domain Name Server) of the website. Your communication language with the salesman is the HTTP(Hypertext Transfer Protocol) which defines the language for interaction between the client and the server. Your order request is the HTTP request and the update on item availability is the HTTP response.
When the web browser sends a request to the server with the DNS of the website, and the server approves the client request, it sends a 200 OK success message. This message means that the server has located the website and it sends back the website files in small chunks of data to the browser. The browser then collects and assembles these small chunks to form the complete website and displays it to us.
We will discuss more on the HTTP request and response in the next articles.
Client-server architecture has the following four types -
In 1-tier architecture, the business logic, data logic, and the user interface all reside on the same machine. The environment is simple and cheap because the client and the server lie on the same system, but the data variance leads to the repetition of work. Such systems store data in a local file or a shared driver. Examples of 1-tier applications are the MP3 player or the MS Office files.
The 2-tier architecture provides the best environment in terms of performance due to the absence of any intervening server. The user interface resides on the client side while the database on the server side. The database and business logic can be stored either at the client or the server end, but they must remain unchanged. If both reside at the client end then the architecture is called fat client thin server architecture. On the contrary, if both reside at the server end, the architecture is called thin client fat server architecture. An online ticket reservation system generally uses a 2-tier architecture.
The 3-tier architecture involves a middleware used for interaction between the client and the server. Though it is expensive but is very easy to use. The middleware improves performance and flexibility. It stores the business and the data logic. The three layers in the 3-tier architecture are-
Almost all web applications are examples of 3-tier architecture.
The n-tier architecture is the scaled form of 3-tier architecture. In such an environment, the processing, data management, and presentation function are isolated in different layers. The isolation makes the system easy to manage and maintain. This is also referred to as multi-tier architecture.
Client-server architecture has the following advantages -
Client-server architecture has the following disadvantages -
So, what are the takeaways from this article?
In the previous article, we have already seen what the client-server architecture is and how it works using the HTTP communication protocol. Moreover, we know that HTTP (HyperText Transfer Protocol) is a TCP/IP communication protocol used for data exchange on the web. Subsequently, now we will understand more about HTTP Request, which is one of the fundamental units used in API testing. Consequently, in this article, we will cover below points-
As already understood in the client-architecture model, the client sends the request to the server to fetch some information or data. Additionally, the client's request is an HTTP Request, which communicates between the client and the server, or you may say, two different computer systems. Moreover, it is a simple text file formatted in either XML or JSON, which sends the client's binary data to the server. A simple GET HTTP request looks like below-
Moreover, the request URI in this case is -
https://bookstore.toolsqa.com/BookStore/v1/Books
HTTP request methods specify the action to perform through the request. These are also known as verbs and generally used for CRUD operations, i.e., Create, Read, Update & Delete. Moreover, HTTP request methods are case-sensitive and should always be uppercase. Subsequently, let us see some commonly used HTTP methods:
1. GET - As the name suggests, the Get method fetches the information from the server. Moreover, it is the most commonly used method which does not have a request body. Every time you open a website, the Get request fires to retrieve the website contents. Additionally, it is equivalent to the read operation. Some of the main features of the GET method are-
An example GET request would look like this -
GET https://bookstore.toolsqa.com/BookStore/v1/Books HTTP/1.1
2. HEAD: The Head method is similar to the Get method, but it retrieves only the header data and not the entire response body. Moreover, we use it when you need to check the document's file size without downloading the document.
3. POST: The Post method works to send data to the server. You may add or update data using the Post request. We send the information that needs to update in the request body. In the real world, the form data on website updates using the Post request. Some of the critical features of a POST method are-
4. PUT: The Put method is similar to the Post method since it updates the data. The only difference is that we use it when we have to replace an existing entity completely. Also, PUT methods are idempotent, i.e., they return the same result on executing repeatedly.
5. PATCH: This method is again similar to Post and Put methods, but we use it when we have to update some data partially. Moreover, unlike the Post and Put methods, you may send only the entity that needs updation in the request body with the Patch method.
6. DELETE: Like its name, the Delete method deletes the server's representations of resources through the specific URL. Additionally, just like the Get method, they do not have a request body.
7. OPTIONS: This is not a widely used method when compared to other ones. It returns data specifying the different methods and the operations supported by the server at the given URL. Moreover, it responds with an Allow header giving a list of the HTTP methods allowed for the resource.
Let us now see how the structure of a simple HTTP request looks.
The next step is to understand how an HTTP request looks like and how it is structured. An HTTP request consists of-
Let us now see what these components are, but before doing that, let's see a simple BookStore REST API developed by ToolsQA.
On combining the above two parts, our Request URL becomes-
https://bookstore.toolsqa.com/BookStore/v1/Books
Now you need to open a browser window and hit this URL. Subsequently, you can open the browser developer tools and navigate the Network tab as highlighted in the image below before you hit the URL.
Once you hit the URL, you will see some requests being sent; you need to click on the first one highlighted below and see that a detailed request description shows up on the right side.
Conclusively, we are now ready to understand the different components of the HTTP request.
Request Line is the very first line in an HTTP request. The combination of three parts forms it-
Looking back at our example, the Request-Line for our case would look like this:
GET /BookStore/v1/Books HTTP/1.1
As discussed above, the HTTP Method specifies the action that should perform through the request. Moreover, the most common methods that you would come across are GET or POST. Subsequently, we will see the difference between the two later in this post.
The Request URI, i.e., the Uniform Resource Identifier, helps identify the resources on which the request applies. Request URI has a general format, as shown below.
Request-URI = "*" | absoluteURI | abs_path | authority
Let us see some examples of how one can use the above request URI.
OPTIONS * HTTP/1.1
GET https://www.toolsqa.com/ HTTP/1.1
GET /BookStore/v1/Books HTTP/1.1 Host: bookstore.toolsqa.com
The HTTP Protocol version allows the sender to specify the format of the message as well as its ability to understand further communications. Additionally, HTTP/1.1 is the standardized protocol version that we use commonly.
Using the request header, the client can send additional information to the server about the request as well as the client itself. Additionally, there can be either zero or more headers in the request, which can define the content type, authorization specification, Cookie information, etc. Moreover, we can also define the custom headers using the x-syntax as per requirements. The below snapshot shows the different headers for the HTTP Request we are using as an example. Additionally, the Request Line is also in a highlight.
In the above request, there are some header fields that you will come across quite frequently while testing APIs. Consequently, let's have a quick look at them-
Other than the above, some of the common request-header fields used as per the requirement are-
The request body sends additional information to the server to fetch data from the request correctly. In our example, we did not send any Request body after the request header, as shown in the snapshot above. Additionally, the request body may either be in the form of XML or JSON. Subsequently, in the upcoming articles, we will see these requests in more detail.
The difference between the GET and the POST method is the most common question in the interviews. Let us see some main points that differentiate both these methods.
GET | POST |
---|---|
Data goes through the header | Data flows through the request body |
The size of data is limited to 255 characters | No limit on the size of data |
Since the data passes through the URL, it becomes insecure | Data is secure since it is not exposed |
GET requests wait for the response of the previous request before sending the next request | The POST request does not wait for a successful response from the previous request before hitting the next one. |
Can be cached | It cannot cache. |
Can be bookmarked | It cannot bookmark. |
Better in performance as compared to POST since the values append to the URL by default | Less efficient in performance as compared to GET as we spend some time on including request body values in the POST request |
Only string data type is allowed | No restriction on data type |
Parameters get stored in browser history | Parameters don't get stored in browser history |
No impact on data if we hit the reload button. | The form data reset if we hit the reload button. |
Uses application/x-www-form-urlencoded encoding | Uses application/x-www-form-urlencoded or multipart/form-data for binary data |
The interaction between the client and the server propagates through the HTTP protocol. Moreover, we have already understood one end of this communication, i.e., the HTTP Requests in our previous article. Subsequently, we now need to understand the other end, which is the HTTP Response. Consequently, in this post, we will cover below points-
HTTP Response is the server's information as a result of the client's request. Additionally, it acts as an acknowledgment that the performance of the requested action is successful. In case there is an error in carrying out the client's request, the server responds with an error message. Moreover, the HTTP responses come as plain text formatted in either JSON or XML format, just like the HTTP requests. In the next section, let us see how an HTTP response looks.
By looking at the various status codes above, we can understand the response from the server. However, the HTTP Response not only contains the response status code but some other components as well. We will discuss them in this section. The composition of HTTP Response looks like this-
Subsequently, let us understand these different components using the same API that we used in the HTTP Request article. Additionally, to reiterate, we will be using the below URL-
https://bookstore.toolsqa.com/BookStore/v1/Books
Once you hit the above URL in your browser window and open the Network tab in the developer tools parallelly, you will see some printed messages. Additionally, you may select Books as highlighted in the image below-
Consequently, let's move ahead and understand the components.
The status line in an HTTP response consists of 3 parts-
It is the first line in the response section. In addition to that, below is the Status-Line for the response we received to our request.
As already discussed in the previous post, the HTTP protocol version specifies the message format along with the sender's ability to understand further communication. Similar to the HTTP request, the HTTP protocol version in the response is 1.1.
Every HTTP response comes with a status code. Moreover, this status code is a 3-digit integer value. The first digit represents the class of the response. Moreover, the last 2 digits do not have any categorical value. There are five distinct values that the first digit of the status code can take. In our example, the status code is 200. But that is not the only status code your response may come with. Additionally, the below table summarizes the different status codes you might come across.
Reason phrase gives/ provides a short textual description of the status code. It is like a status text that resonates with the status code. In our example, the reason phrase is OK.
A response header is similar to the request header, which consists of additional information about the response. Additionally, a response may have 0 or more headers, but it is highly unlikely to have zero response headers. Moreover, the response headers are present after the status line and before the response body. Consequently, in our example, the response header is as below-
Let's briefly discuss the different response headers-
The response body consists of the resource data requested by the client. In our example, we requested the book's data, and the response body consists of the different books present in the database along with their information.
As can be seen, the book list comes in response along with different book properties like its author, description, isbn, title, etc. Additionally, the message body helps the user with more information on the response, be it a valid response with data, some error in response, or the need for additional data from the client.
As seen above, Status codes separate into 5 different categories. Consequently, let's see those in details:
Code | Description |
---|---|
1xx | Information, i.e., it denotes that the request has been received and under process. |
100 | Continue: The client can continue with the request as long as it doesn't get rejected. |
101 | Switching Protocols: The server is switching protocols. |
2xx | Success, i.e., it denotes a successful receipt, processing, and acceptance of the request message. |
200 | OK: The request is OK. |
201 | Created: A successfully created new resource |
202 | Accepted: Request accepted for processing, but in progress |
203 | Non-Authoritative Information: The information in the entity header is not from an original source but a third-party |
204 | No Content: Response with status code and header but no response body |
205 | Reset Content: The form for the transaction should clear for additional input |
206 | Partial Content: Response with partial data as specified in Range header |
3xx | Redirection, i.e., further action has to be taken for the request to complete |
300 | Multiple Choices: Response with a list for the user to select and go to a location |
301 | Moved Permanently: Requested page moved to a new url |
302 | Found: Requested page moved to a temporary new URL |
303 | See Other: One can find the Requested page under a different URL |
305 | Use Proxy: Requested URL need to access through the proxy mentioned in the Location header |
307 | Temporary Redirect: Requested page moved to a temporary new URL |
4xx | Client Error, i.e., incorrect syntax or error in fulfillment of the request |
400 | Bad Request: Server unable to understand the request |
401 | Unauthorized: Requested content needs authentication credentials |
403 | Forbidden: Access is forbidden |
404 | Not Found: Server is unable to find the requested page |
405 | Method Not Allowed: Method in the request is not allowed |
407 | Proxy Authentication Required: Need to authenticate with a proxy server |
408 | Request Timeout: The request took a long time as expected by the server |
409 | Conflict: Error in completing request due to a conflict |
411 | Length Required: We require the "Content-Length" for the request to process |
415 | Unsupported Media Type: Unsupported media-type |
5xx | Server Error, i.e., error invalid request fulfillment at server side |
500 | Internal Server Error: Request not completed due to server error |
501 | Not Implemented: Server doesn't support the functionality |
502 | Bad Gateway: Invalid response from an upstream server to the server. Hence, the request not complete |
503 | Service Unavailable: The server is temporarily down |
504 | Gateway Timeout: The gateway has timed out |
505 | HTTP Version Not Supported: Unsupported HTTP protocol version |
Subsequently, in the upcoming article, we will see some more concepts that will add to our base of learning RestAssured.
The world wide web has become too complex today. With hundreds and thousands of electronic media talking to each other, the process of communication has become too complex and heavy on computation for sure. Such scenarios call for something that can ease out the communication and keep the coding complexity to a minimum as well. That something is the focus of this post and is termed as REST.
REST, or REpresentational State Transfer, is an architectural style and provides standards between the systems on the web. Popularly, RESTful systems use the REST style to communicate with each other. So in many places, you might see "RESTful" over the internet and it means the same thing. In this article, we dwell in more details about REST basics and will cover the following topics in this article.
REST (REpresentational State Transfer) was first presented in the year 2000 by Roy Fielding as an architectural style for distributed hypermedia systems. REST-compliant or RESTful systems, are "stateless" (discussed later in this article) and separate a client and a server. A web application developed using REST (RESTful web application) exposes the data or information about its resources which can be anything that the developer wants. This allows the client using this application to take action on the resources. For example, using information exposed to users, clients can create a new user.
As another example, suppose we have a pet store and the information related to all pets in the store is stored on the server. If we want information related to a pet (let's say pet with id=0) then we will access the appropriate URL of the pet store in the browser (Here the browser will be the client). An example URL can be "https://demoqa.com/BookStore/v1/Books".
The above interface looks as shown below:
All the methods (coloured buttons) shown in the above screenshot correspond to REST APIs that are executed with the click of a button. Please note that this is a demo website and therefore the APIs are so explicit on the screen. In general, all this is done in the back-end and the client is not shown any of this information. Coming back, when we click the "GET" button (/Books), we will get the response in JSON format that will show us the details of the particular book. This response is shown below:
Note: When we click on the "GET" button, in the browser we see the link changes to "https://demoqa.com/BookStore/v1/Books".
We will discuss all the methods shown above (in form of buttons) in our subsequent articles.
Now let us depict the actual REST data transfer in the above example in the diagram below.
As the above diagram shows, the API works as a medium of communication between a database (that is a part of a server) and a client. When a client sends data through APIs, it goes to the database, do the appropriate operation (such as add, delete, modify etc.) and return the response data that contains response code, header files, cookie info etc.
We can summarise the REST characteristics and the working in the above diagram as follows:
REST is a way to access a web service and is often viewed as an alternative to SOAP (Simple Object Access Protocol).
In a RESTFul application, we have entities namely client and resource which are used commonly. Let us discuss them briefly next.
A client can be a software or a person or a system using the APIs to access data from another application server. For example, a developer might access Facebook APIs to embed a live post in their own website. The developer program will call the Facebook API through a browser. So in this case, the browser acts as a client that is calling the Facebook APIs.
If we visualize this system using the REST diagram above, the client or browser will connect to Facebook-Server over REST API and then get the information required to render it on the screen.
As another example, suppose I have an application "myHealthApp". I want data on the Covid-19 pandemic from a city, Pune, for example. To achieve this, I will develop APIs or methods, such as, "Corona API" using which I can request the data from Pune Municipal Council (PMC). This is done using myHealthApp. This means using myHealthApp which acts as a client, I will make a request for data using Corona API (say getCoronaData method to be more specific) through the browser ( for example, https://myHealthApp/getCoronaData). This getCoronaData method will in turn connect to PMC servers and fetch the required data as a JSON response (or any other format). On the client side, I can take that data and perform various operations on it. This is the use of APIs.
Any smaller unit that can be transformed and addressed through a URL and HTTP method is considered a resource. This resource makes changes to the database. For one application, you may have a lot of resources with all of them assigned a particular task. For example, an online book store may have a resource as a table of the database. A resource in a REST architecture is anything that a client has access to and can modify or update.
In a way, you can say that a resource is any object for which we need information from API. So in the above example about Facebook, a resource can be a post, page, or user account. In the example of Corona API, resources are all the details on Corona like Corona data, pages on treatment, vaccination, etc.
For an API to be RESTful, it has to fulfil or adhere to the following guiding principles or constraints defined by REST.
The uniform interface principle has the following parts that an API has to follow:
The interaction between the client and the server is independent and is only in the form of requests. The client initiates a request and the server sends the responses. The response is a reaction to the request. So all server does is wait to receive requests from the client. It never sends out information about the resources to the client on its own. For more details on client-server refer to our tutorial, Client-server Architecture.
The word "stateless" means the server does not keep track of the user who uses the API. So when a new request comes in, the server will not know if the same user has sent a GET request in the past for the same resource. It doesn't remember the resources requested by the user earlier. For example, HTTP is a stateless protocol. HTTP server does not keep track of any state of information passed to and fro. Hence at any given time, a client can send a valid command, and the server will not relate or keep track of any previous similar commands.
Hence each request regardless of the other requests made by the user will contain all the data needed by the server to execute the request and send a response.
A layered system provides a hierarchical structure between a client and a server. There can be a lot of intermediaries between the client and the server working along with REST API without the client's notice. Our clients think there is a direct connection to the server. We then take advantage of it to improve our architecture and bring down our distributed system complexity. These intermediate elements provide a security layer, load-balancing layer, and other functionality to the system. The only guideline is that the presence of these intermediate layers should not affect the request or response.
Note: The abstraction of layers does not let one layer be aware of the presence of another layer.
The server data received in the response contains information regarding whether the data is cacheable or not. If the data is cacheable, it will contain some kind of version number that makes caching possible. The client will know which version of the data it has got from the previous response. This way client can avoid requesting repeated data. Cacheable data (and therefore version number) also helps the client to know the expiration of version data and the requirement of a new request to fetch the latest data.
This particular constraint "Code on demand" is optional and without fulfilling it we can have a RESTful API.
The client can send a request to the server asking for the code and then the server will respond with some code in the form of a script or some other entity. For example, servers can extend the client functionality by downloading and executing pre-compiled code like an applet or a client-side script like JavaScript. So when we click on any video on Facebook, Facebook will run a precompiled or any third-party software to run that video.
Once an API fulfils the above constraints we discussed, we can say it is a RESTful API.
In this article, we discussed the introduction of the REST protocol for web services.
In our last article, we discussed what is REST and the various constraints that should be adhered to by a RESTful application. All our knowledge gathered regarding the REST architecture can be applied in performing operations with REST API. In this article, we will cover the same with the following topics in the highlight.
An API (Application Programming Interface) conforming to the REST architectural style or REST standards is known as a "REST API". A Rest API facilitates interaction between the client and RESTful web services*( server)* to get the required information. They can be used for a variety of purposes by interacting with the system. These may include specific actions such as retrieving the location of a particular city or data updation such as registering a new user. API developers use REST standards in a variety of ways to develop REST APIs. The following diagram shows a general REST API functionality.
As shown in the above diagram, REST API sits in the middle layer to the database and the presentation layer i.e. the interactive systems. The other applications (shown as the top layer) will call the REST API that has centralized core logic in one place. The applications call REST APIs to access the desired data. For example, if we try to hard code everything, we need to code for each action on the website. Retrieving book by book serial number is an action that may need to run through the middle layer and then the database. It can take a lot of time depending on the size of the database and website. With RESTful APIs, the process becomes much faster as they are lightweight. So instead of writing separate code and logic for each application, we write REST APIs accessible by any application.
When a client makes a specific request, RESTful API transfers a state representation of the resource to the requester or endpoint. The format of this representation is one of several formats like HTTP: JSON (Javascript Object Notation), HTML, XLT, Python, PHP, or plain text. The most popular format generally used nowadays is JSON since it is easy to read and is very lightweight.
Headers and parameters also play an important role in the HTTP methods of a RESTful API HTTP request. They contain important identifier information regarding the request's uniform resource identifier (URI), metadata, authorization, caching, cookies, etc. These request and response headers have their own HTTP connection and status code information.
Next, let us move on to why we use REST APIs.
We mainly use REST API for the following reasons:
We perform CRUD Operations when we are working with web technologies and applications. As shown in the below figure, CRUD stands for Create, Read, Update, Delete. This means using CRUD operations, we can create, read, update and delete a resource. Generally, we make use of HTTP methods to perform CRUD operations. In the case of REST API methods, the REST provides these four methods in the form of APIs. Refer to the figure below:
As shown above, POST, GET, PUT and DELETE are the HTTP methods used for CRUD operations. The following table shows the description of each of these methods as well as an example URL using the swagger tool https://demoqa.com/swagger/
HTTP Method | Operation | Operation Type | Example URL |
---|---|---|---|
GET | Get the list of books | Read Only | curl -X GET "https://demoqa.com/BookStore/v1/Books" -H "accept: application/json" |
POST | Add list of books | Non-Idempotent | curl -X POST "https://demoqa.com/BookStore/v1/Books" -H "accept: application/json" -H "Content-Type: application/json" -d "{ "userId": "toolsqa_test", "collectionOfIsbns": [ { "isbn": "9781449325862" } ]}" |
PUT | Replace ISBN object with given ISBN | N/A | curl -X PUT "https://demoqa.com/BookStore/v1/Books/9781449325889" -H "accept: application/json" -H "Content-Type: application/json" -d "{ "userId": "toolsqa_test", "isbn": "9781449325862"}" |
DELETE | Delete book with given ISBN | Idempotent: Same results irrespective of how many times the operation is invoked. | curl -X DELETE "https://demoqa.com/BookStore/v1/Book" -H "accept: application/json" -H "Content-Type: application/json" -d "{ "isbn": "9781449325862", "userId": "toolsqa_test"}" |
So how do we test these methods in a Swagger tool? Let us see an example of GET operation. Navigate to the following link: https://demoqa.com/swagger/#/BookStore/BookStoreV1BooksGet
We can see the following REST API for GET book details.
Now let us "execute" the GET operation that "gets book details". When we click this button and execute the API, the below-given command or GET syntax gets executed:
curl -X GET "https://demoqa.com/BookStore/v1/Books" -H "accept: application/json"
On performing the above operation we get the following response.
In a similar manner, we can perform other operations as well for which we have crafted dedicated posts. In subsequent articles, we will learn to create a REST API ourselves.
So here are some of the points we should remember for REST API methods.
In this article, we have given the general idea of a REST API.
In subsequent articles, we will dig into more REST architecture and APIs.
In our previous article, we have thoroughly learned about REST services and the set of constraints that bound us to industry standards. Additionally, we may employ specific architectural elements in our software application to get the desirable results with optimum performance. These REST architectural elements form the key conceptual elements of a Restful service. The below representation shows the key architectural elements along with the constraints in a REST architecture-
In this article, we will focus on these three different classes of the architectural elements identified by REST :
REST uses connectors that manage communication between the different components. There are five types of connectors that encapsulate the activities of accessing resources. They also enable transferring of resource representations. All the communication through the connectors is hidden with clean separation thereby enhancing the simplicity. There are following REST connector types-
Components in REST architectural elements are the different software that interacts with each other. They act as end-points connected through connectors and exchanging data elements. There are mainly categorized into four types as described below-
The uniform interface between the components of REST architectural style distinguishes it from all other network-based architectures. It makes all the interactions between the client and the server unified through a fixed set of operations. Data elements and their state form the key aspect of REST architectural elements. The components interact by exchanging the representation of the current or desired state of data elements.
REST identifies six data elements as described below-
A resource is any addressable information on the server. It is a conceptual mapping to a set of entities and not the data itself. This mapping can either be static or temporal. A static resource is one that remains unchanged over a long period of time like a web page address. Alternatively, a temporal resource keeps changing with time, as the weather information about a city.
A resource identifier uniquely identifies a resource also known as URI. The URIs and resources follow a many-to-one relationship. It means that a resource can have multiple URIs providing differing information about the resource. An example of resource identifier Bookstore API by ToolsQA. The resource identifier for getting the list of available books is- https://bookstore.toolsqa.com/BookStore/v1/Books On accessing the above resource identifier you will be able to see the resources, i.e the list of books present in the database.
It describes the different properties of a resource by providing additional information like its location or the alternate resource identifier. It categorizes the resource and gives information about it.
Representation is the state of a resource and is data plus the metadata which explains the data. In a real sense, we do not directly send or receive the resource. Rather, we request and receive the representation of resources. A representation can either be a document, a file, or even an HTTP message entity. XML and JSON are the most popular resource representations. In our example of bookstore API, the resource representation is in JSON format.
This data element, resource metadata, describes the format type of the response body. This helps in the interpretation of data at the client end. The representation metadata consists of the different headers used in the request and response. We have explained all the header types in our previous posts on HTTP request and HTTP response. But let us still quickly see what all headers are included as representation metadata in our example API.
As can be seen, The resource request defines the content-type as a JSON and also specifies the content-length to set the limit. The response headers also identify the additional information for the response like the User-Agent, Connection, Accept-Encoding, etc
It defines the purpose of messages exchanged between the client and the server such as the requested action and response meaning. It also helps parameterize requests and override the connecting elements' default behaviour. For example, control data can modify the cache behaviour of a request or the response. Additionally, the data corresponding to a certain condition like the last modification date(If-Modified-Since) or if it matches a certain parameter(If-Match) can be fetched.
In our previous articles, we have discussed the basics of the REST approach including familiar terms, architectural elements, etc. Moving one step ahead in this direction, let us go ahead with API testing using REST in this article. The most popular technology we use for API testing is "Rest Assured". We will cover the following topics related to REST Assured in this article:
Rest-Assured is an open-source Java-based library useful for testing and validating REST APIs or RESTFul Web Services. It is like a headless (no GUI) client that accesses and tests the REST Web Services. It was developed and maintained by Johan Haleby with the support of several other contributors.
Rest assured simplifies the testing of REST-based services. It brings out the simplicity of dynamic languages like Groovy and Ruby that perform API testing in Java. The library supports any HTTP method and also has explicit support for GET, POST, PUT, DELETE, OPTIONS and HEAD. It also includes specifications and validation like parameters, headers, cookies, etc. We can also use it to verify and validate the response of the HTTP requests.
Apart from testing JSON-based web services, Rest Assured can also be used to test XML-based web services. We can also integrate this library with JUnit and TestNG frameworks and write test cases for applications. Further, it can be integrated well with Maven and its efficient matching techniques produce straightforward results.
Yet another powerful feature of REST assured is its support for XML Path and JSON Path syntax to check specific elements of the response data similar to using XPath API. For people new to such concepts, please refer to the following syntax examples.
XPath syntax:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book>
<title>Freedom In Exile</title>
<price>14.29</price>
</book>
</bookstore>
JSON path syntax:
$['store']['book'][3]['title']
Apart from all the above features, this fantastic library also provides various other features like DSL-like syntax, Specification Reuse, XPath-Validation, easy file uploads, and all the features that are conducive to automated testing.
The above discussion about this java library convinces us that it is a reliable library. But why do we need it or what are the reasons to use it in our application testing?
Following are the main reasons we need Rest Assured :
Following are some of the versions of the Rest Assured library we use generally in applications.
The following table lists some of the advantages of the library.
SNo | Advantages |
---|---|
1 | It is open-source and hence free to use. |
2 | It is very rich in syntax and ready-made assertions. Rest Assured requires less coding as compared to Apache HTTP Client. |
3 | The setup of Rest Assured is easy and straightforward. |
4 | The response is given in JSON or XML format and is easy to parse and validate. |
5 | It uses inbuilt Hemcrest Matchers for easy extraction of values. |
6 | Response time is quick as also an assertion of status code. |
7 | The library has a powerful logging mechanism. Also, we can verify headers, cookies, content type, etc on the fly. |
8 | It can easily be integrated with other Java libraries like TestNG, JUnit, etc. We can also integrate it with Selenium-Java and achieve end-to-end automation. |
9 | It has very good support for various API authentication mechanisms. |
10 | It supports JsonPath and XmlPath that helps in parsing JSON and XML response. It also has support for the JSON Schema Validation library to verify JSON Schema. |
11 | Rest Assured can also be integrated with Maven and CICD. |
12 | It supports multi-part form data |
13 | Supports Spring Mock MVC, Spring Web Test Client, Scala, and Kotlin. |
14 | It follows the BDD (Behavioural Data-Driven) approach and keywords like given() when(), then() which makes code readable and supports clean coding. This feature is available from version 2.0. |
15 | REST Assured 4.1.2 adds support for Java 13. |
The library has the following disadvantages.
We have the following takeaways from this post:
With this knowledge, we will begin with actual API testing tutorials in our subsequent articles. Before that, we will discuss the configuration of Rest Assured with Eclipse IDE.
In the last tutorial, we went through the concepts of Rest-assured in theory. To start our practical journey, we need to write code that can send and receive the data with a lot more interesting twists in between. However, the medium that will help us in our API testing is an integrated development environment (IDE). In our case, we will be using the popular Eclipse IDE and in this post, we will prepare it for our coding exercises. In other words, we will configure eclipse with rest-assured for API testing. The index looks as follows:
The first step towards setting up the development environment so that we can test some RESTful APIs is to evaluate the prerequisites. The following list highlights the required software for setting up Eclipse with rest-assured. Before proceeding, please make sure they are installed on your system.
If any of the above is not present on the system, then we have to follow the links given below :
Once the prerequisites are up and running we can proceed with the rest assured set up on Eclipse.
The rest assured jar files can be downloaded from the following link.
https://github.com/rest-assured/rest-assured/wiki/Downloads.
When we navigate to this page, we can see the section “Current direct downloads”. Go to this section and click on the link to the dist package.
Note: At the time of this writing Version 4.4.0 is available for download.
Once the link is clicked, the browser will download the rest-assured-4.4.0-dist.zip file on the system (mostly in the "downloads" folder for Windows OS). Navigate to the folder where the zip file was downloaded and simply unzip the files.
Now go to the folder where the files were just unzipped and open it. The contents should be the following in this folder.
Note: We have to also unzip the rest-assured-4.4.0-deps.zip dependency jars. In the end, your folder would look like this.
Now that we have rest assured jars package ready, we can go ahead and create a project in Eclipse that will use rest assured.
To add and use unzipped rest assured jars in an actual project, we have to first create a new project in Eclipse. So we create a project, let's say, RestAssuredTest as shown below.
Once clicked on the Java Project link, a new project window opens up. Enter the name of the project as RestAussuredTest and click on the Finish button. Once Finish is clicked, we get the following screen. Click Open Perspective. (Alternatively, if we have already checked the option "remember my decision", then this step will not be seen)
Now the project with the name RestAssuredTest should display in the package explorer.
Once the project is created in Eclipse, now it is required to add the unzipped jars in the classpath of the Eclipse project.
Select the Java project folder we just created and right-click on the project folder in the package explorer pane and choose Properties. This will open up the project properties pop-up window as shown below.
Select Java build path option in the left pane of the properties window. In the Java build path pane, on the left-hand side, you will see the Libraries pane.
Here we are going to reference the rest assured jar files that we downloaded earlier. To reference the jars, click on the "Add external jars" button and navigate to the folder where we unzipped all our rest-assured jars. Refer to the image shown below.
Now include the following Jars
With this, we have successfully set up a Java project in Eclipse with a rest-assured library. If we now check the project explorer, we will get the following view.
Now we are ready to use Rest Assured features in Eclipse.
In this article, we configured the Eclipse project with Rest Assured Library.
With these steps, we can configure Rest Assured with the Eclipse project. In the next article, we will create a simple REST API with the Rest Assured library.
In our last article, witnessed the steps to configure Eclipse in our last article titled Configuring Eclipse with Rest-assured. Continuing on the same path, in this article, we will discuss writing our first API test using Rest Assured. Along with that, we will also discuss the basics of REST API testing briefly in this chapter. This chapter assumes that the reader has good knowledge of the TestNG Framework. Thus we will cover the following topics in this article.
Rest stands for Representational State Transfer and is an architectural style for communication with web services. An API or Application Programming Interface is a set of programming instructions used to access a web-based software application. The APIs built using REST is called REST APIs which we have discussed in an earlier article. Let us now begin to understand REST API testing.
So what is REST API testing?
REST API testing is a technique to test RESTful APIs and validate their correctness. We send the request (preferably using automation) and record the response for further assertions. This way we can check if the REST API is working fine or not. REST API testing is mainly done using four REST methods, viz, GET, POST, PUT, DELETE.
There are two approaches to test the REST API:
However, in this tutorial, we will only concentrate on automated REST API testing.
We know that REST API uses five HTTP methods to request a command:
Method | Description |
---|---|
GET | Retrieves the information at a particular URL. |
PUT | Updates the previous resource if it exists or creates new information at a particular URL. |
POST | Used to send information to the server like uploading data and also to develop a new entity. |
DELETE | Deletes all current representations at a specific URL. |
PATCH | This is used for partial updates of resources. |
Once the request is sent using the above methods, the client receives the numeric codes known as "Status codes" or sometimes referred to as "Response codes". Then we can interpret these status codes to know what kind of response the server has sent for a particular request. Status codes are mainly classified into five categories as shown in the table below.
No | Status Code | Description |
---|---|---|
1 | 1xx (100 – 199) | The response is informational. |
2 | 2xx (200 – 299) | Assures successful response. |
3 | 3xx (300 – 399) | You are required to take further action to fulfil the request. |
4 | 4xx (400 – 499) | There’s a bad syntax and the request cannot be completed. |
5 | 5xx (500 – 599) | The server entirely fails to complete the request. |
The above codes help us to interpret the outcome of the HTTP requests. From the above table, we can deduce that if the response status code is 2xx, it means the application is functioning as it should be. The status code 1xx, 2xx, 3xx are not considered errors but are informative messages and these codes will not affect the user experience.
However, if we get status codes like 4xx and 5xx, these are error messages. This means users will encounter error messages when they are navigating through the APIs. Errors at the client or browser level mostly result in 4xx status code error messages. Whereas server-level errors result in 5xx status code error messages. So when performing REST API testing we should evaluate each response by inspecting the error codes.
Consider the following REST API example URL, https://demoqa.com/swagger/#/BookStore. This is a bookstore inventory and it provides us with various REST API methods to access information from the bookstore. Now let us access this URL in the browser and we are presented with the following screen.
Click on the GET method (the first one) in the above store to access the pet inventory. When we click and execute the GET method, we get the following response.
Note the status code on the above screen (left panel). It is 200 which means the request was successfully executed and we got a successful response. This way we receive a status code from the server when we send a request and then we can interpret this status code and check if the request was executed in a normal manner or some error occurred. We will further explore the response obtained from the server in our upcoming articles.
REST Assured is a Java library for testing RESTful APIs. It is widely used to test JSON and XML-based web applications. In addition, it fully supports all REST methods like the GET, PUT, POST, PATCH, and DELETE. Next, we will see a detailed walkthrough of testing one REST API using the Rest Assured library.
To write a sample REST API test we will make use of the following REST API link.
Request URL | https://demoqa.com/BookStore/v1/Books |
HTTP Method | GET |
Comments | This URL will return the inventory details of a Book store. There are no input parameters for the request. |
Response | {"books": [{"isbn": "string","title": "string","subTitle": "string","author":"string","publish_date": "2022-01-25T13:44:50.276Z","publisher": "string","pages": 0,"description": "string","website": "string"}]} |
In fact, if we directly open the above URL in the browser, we get this output as shown below:
To get the same output programmatically using the Rest Assured library, we have to follow below steps:
Below given is the complete code for the above steps:
import io.restassured.RestAssured;
import io.restassured.http.Method;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
public class RestAssuredAPITest {
@Test
public void GetBooksDetails() {
// Specify the base URL to the RESTful web service
RestAssured.baseURI = "https://demoqa.com/BookStore/v1/Books";
// Get the RequestSpecification of the request to be sent to the server.
RequestSpecification httpRequest = RestAssured.given();
// specify the method type (GET) and the parameters if any.
//In this case the request does not take any parameters
Response response = httpRequest.request(Method.GET, "");
// Print the status and message body of the response received from the server
System.out.println("Status received => " + response.getStatusLine());
System.out.println("Response=>" + response.prettyPrint());
}
}
The above code generates the same response as we got in the browser earlier. The following screenshot shows the response.
In this way, we can make any Test API call and get the response from the webserver hosting RestFul services.
Let us now walk through the code we implemented above.
// Specify the base URL to the RESTful web service
RestAssured.baseURI = "https://demoqa.com/BookStore/v1/Books";
The above line uses RestAssured class to set up a base URI. In this case, the base URI is “https://demoqa.com/BookStore/v1/Books”. The base URI indicates the root address of the resource we are going to request from the server (hence the name base URI). Then we will add parameters, if any, when we actually make the request in subsequent code.
The class io.restassured.RestAssured , is the basis of any kind of HTTP request we make for tests. Some of the key features of this class are:
Internally io.restassured.RestAssured class uses an HTTP builder library, which is a Groovy language-based HTTP client.
// Get the RequestSpecification of the request to be sent to the server.
RequestSpecification httpRequest = RestAssured.given();
The next line gets the RequestSpecification of the request to be sent to the server. Rest Assured library provides an interface called RequestSpecification for this purpose. The variable httpRequest stores the request so that we can modify it if required like adding authentication details, adding headers, etc. For this particular test, we are not modifying the variable.
// specify the method type (GET) and the parameters if any.
//In this case the request does not take any parameters
Response response = httpRequest.request(Method.GET, "");
Now we call the server to get the resource using the RequestSpecification object. The above code line uses the request method to send the request for the resource to the server.
The request method takes two arguments, the first is the HTTP method and the second is a string. The string parameter is used to specify the parameters that are to be sent with the base URI. In this case, to get pet store details we do not send any parameters hence the blank string. The return type of the request method is the Response object which means the request method gets the response back from the server.
The Response interface (io.restassured.response.Response) represents a response returned from a server. It contains all the data sent by the server. As we will see in the subsequent articles, we can call different methods on this response object to extract the response like response status, headers, etc.
// Print the message body of the response received from the server
System.out.println("Status received => " + response.getStatusLine());
System.out.println("Response=>" + response.prettyPrint());
In the above code lines, we just read the response as a string and print it to the console. We use the getBody method of response interface that returns the actual body of the response. This is then printed to the console.
We can also write the above test code using short-hand methods provided by Rest Assured. Following is the code snippet that is shortened a bit.
@Test
public void GetWeatherDetailsCondensed()
{
// Specify the base URL to the RESTful web service
RestAssured.baseURI = "https://demoqa.com/BookStore/v1/Books";
// Get the RequestSpecification of the request that is to be sent
// to the server.
RequestSpecification httpRequest = RestAssured.given();
// Call RequestSpecification.get() method to get the response.
// Make sure you specify the resource name.
Response response = httpRequest.get("");
// Response.asString method will directly return the content of the body
// as String.
System.out.println("Response Body is => " + response.asString());
}
So here we use the "get" method on the RequestSpecification object that returns the Response object.
We will refer to the same example in our next articles when we go into details of the io.restassured.response.Response interface.
Note: The postman article on the same topic (API test and GET request) can be found at New Request in Postman and Get Request in Postman.
Visit Get Request in Rest Assured for the video tutorial.
In this article we have discussed:
In our next article, we will discuss the response part of the Rest API implementation.
In the previous tutorial of Rest-Assured Test, a test made a call to Weather Web Service. This tutorial will also continue the same example.
Functional testing of Web Services involves verifying the Responses returned from various End Points. Some of the important Test Verification are:
This tutorial will cover the following verification :
In the next tutorial will cover the verification of Response Body Content.
From the previous First Rest-Assured Test example, a simple test which calls the Weather web service using a Get Method looks like this
@Test
public void GetWeatherDetails()
{
// Specify the base URL to the RESTful web service
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
// Get the RequestSpecification of the request that you want to sent
// to the server. The server is specified by the BaseURI that we have
// specified in the above step.
RequestSpecification httpRequest = RestAssured.given();
// Make a GET request call directly by using RequestSpecification.get() method.
// Make sure you specify the resource name.
Response response = httpRequest.get("/Hyderabad");
// Response.asString method will directly return the content of the body
// as String.
System.out.println("Response Body is => " + response.asString());
}
Response object which represents the HTTP Response packet received from the Web service Server. HTTP Response contains Status, collection of Headers and a Body. Hence, it becomes obvious that the Response object should provide mechanisms to read Status, Headers, and Body.
Response is an interface which lives in the package: io.restassured.response. This interface has lots of methods, mostly methods which can help to get parts of the received response. Take a look at some of the important methods. A simple Response followed by a dot (Response). in eclipse would shown the available methods on the interface. As shown in the image below
Noticed, getStatusCode() method can be used to get the status code of the Response. This method returns an integer and test will verify the value of this integer. TestNG Assert is used to verify the Status Code.
@Test
public void GetWeatherDetails()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("/Hyderabad");
// Get the status code from the Response. In case of
// a successfull interaction with the web service, we
// should get a status code of 200.
int statusCode = response.getStatusCode();
// Assert that correct status code is returned.
Assert.assertEquals(statusCode /*actual value*/, 200 /*expected value*/, "Correct status code returned");
}
Below line of code extracts the status code from the message:
int statusCode = response.getStatusCode();
Once the status code is received, it is compared with the expected value of 200.
// Assert that correct status code is returned.
Assert.assertEquals(statusCode /*actual value*/, 200 /*expected value*/, "Correct status code returned");
Try running the test and notice that the test will pass. This is because the web service indeed returns a status code of 200. Try to print the value of statusCode.
Status codes returned by the Server depends on whether the Request was successful or not. If the Request is successful, Status Code 200 is returned. status code. If the Request is not successful, Status Code other than 200 will be returned. Get a list of HTTP Status codes and there meanings on the W3 page.
For ToolsQA Weather web service, let's create another test which tests a negative scenario. The scenario is
City name that this test will use here is a long number, like 78789798798. Just copy the code from previous test and simple replace the city name with 78789798798. At this moment do not change the status code. The code looks like this
@Test
public void GetWeatherDetailsInvalidCity()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("/78789798798");
int statusCode = response.getStatusCode();
Assert.assertEquals(statusCode /*actual value*/, 200 /*expected value*/, "Correct status code returned");
}
Now run your test class and check the result. Our negative test GetWeatherDetailsInvalidCity will fail. The reason is that the web service returns an error code of 400 when invalid city name is sent to it. However, the test is validating for expected value of 200. Look at the TesNG results, as shown in the image below.
A quick change in the expected result in the code will make sure that this test passes. Update respective line as in the code below
Assert.assertEquals(statusCode /*actual value*/, 400 /*expected value*/, "Correct status code returned");
The first line returned in the Response from Server is called Status Line. Status line is composed of three sub strings
During a success scenario a status line will look something like this "HTTP/1.1 200 OK". First part is Http protocol (HTTP/1.1). Second is Status Code (200). Third is the Status message (OK).
Just as we go the Status Code in the line above, we can get the Status Line as well. To get the Status line we will simply call the Response.getStatusLine() method. Here is the code to do that.
@Test
public void GetWeatherStatusLine()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("/Hyderabad");
// Get the status line from the Response and store it in a variable called statusLine
String statusLine = response.getStatusLine();
Assert.assertEquals(statusLine /*actual value*/, "HTTP/1.1 200 OK" /*expected value*/, "Correct status code returned");
}
Validate Response Header using Rest Assured
REST assured has been of great use to us for testing APIs and validating a lot of things. For instance, in the last tutorials, we performed a sample Test call using Rest Assured and also validated response status. But that is just one part of the overall response testing when it comes to automation. There is a lot more that we need to validate in order to be an efficient tester and test the quality of our APIs. In this article, let us validate the Response Header using Rest Assured with the following topics in focus:
- What is an HTTP Response Header in REST API?
- REST Assured methods for validating HTTP Response Headers.
- How to access and read HTTP Response headers using REST Assured?
- How to validate Response Header using Rest Assured.
What is an HTTP Response Header in REST API?
The response received from the server consists of zero or more headers along with response status and response body. Each header is a key-value pair. The header part of the response is used by the server to send extra information which is also referred to as "Metadata" of the response.
For example, headers contain a "Content-Type" attribute that tells us how to interpret the data of the response body. So if the response body contains JSON data, then the corresponding content-type attribute in the header will be "application/json". Similarly, if the data in the body is XML the Content-Type header will be "application/xml".
For example, for the following request URL: https://demoqa.com/BookStore/v1/Books
when we give the following GET request:
curl -X GET "https://demoqa.com/BookStore/v1/Books" -H "accept: application/json"
we get the following response.
Note the response header that is obtained (red rectangle). Since the body is JSON, the Content-Type is set to "application/JSON".
Rest Assured methods for validating HTTP Response Headers
The Response interface of the Rest Assured library provides methods to access all headers or individual headers. Just typing "Response.head" in Eclipse (or any such editor) will show all the methods supported to access headers.
As shown in the above screenshot, the Response interface of REST Assured provides methods related to headers.
- headers() : returns Headers
- getHeader(): returns a Header
- getHeaders(): returns Headers
When all the headers in a Response are returned, we can print each header by simply iterating over each of them.
Note: The collection of headers is represented by a class called io.restassured. HTTP.Headers. This class implements the Iterable interface. Hence, we can use the "for each (for( : ))" loop to read all the headers.
How to access and read HTTP Response headers using REST Assured?
Now let us see how we can read a Header using Rest-Assured. Let’s write a test to record the following Header Types from the Response:
- Content-Type.
- Server.
- Content-Encoding.
Shown below is the code for this test:
@Test
public void IteratingHeaders()
{ RestAssured.baseURI = "https://demoqa.com/BookStore/v1/Books";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("");
// Get all the headers and then iterate over allHeaders to print each header
Headers allHeaders = response.headers();
// Iterate over all the Headers
for(Header header : allHeaders) {
System.out.println("Key: " + header.getName() + " Value: " + header.getValue());
}
}
In the above code, we access all headers and then extract individual headers by looping through the Headers collection. Shown below is the console output of the above test.
The above output shows all the header key-value pairs that we displayed using for loop in the code.
Let us demonstrate the .header(String arg0) method to get a particular header. Here we pass the exact header name as an argument.
@Test
public void GetBookHeaders() {
RestAssured.baseURI = "https://demoqa.com/BookStore/v1/Books";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("");
// Access header with a given name.
String contentType = response.header("Content-Type");
System.out.println("Content-Type value: " + contentType);
// Access header with a given name.
String serverType = response.header("Server");
System.out.println("Server value: " + serverType);
// Access header with a given name. Header = Content-Encoding
String acceptLanguage = response.header("Content-Encoding");
System.out.println("Content-Encoding: " + acceptLanguage);
}
}
}
In the above code, we are accessing Content-Type, Server, and Content-Encoding headers by specifying respective headers names as an argument to the header() method. Shown below is the console output of the above code.
Note: Response.GetHeader(String headerName) method behaves exactly the same way as the Response.Header(String headerName) method. So the above code can be written by replacing the ".Header()" method with the ".GetHeader()" method.
How to validate HTTP Response Header using Rest Assured?
Now that we have had a discussion about methods that access header, let us write a test to validate the values of the header by putting it as Assert. The below code shows the test that is similar to the code in previous sections but only uses Assert to validate the results.
@Test
public void ValidateBookHeaders()
{
RestAssured.baseURI = "https://demoqa.com/BookStore/v1/Books";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("");
// Access header with a given name. Header = Content-Type
String contentType = response.header("Content-Type");
Assert.assertEquals(contentType /* actual value */, "application/json; charset=utf-8" /* expected value */);
// Access header with a given name. Header = Server
String serverType = response.header("Server");
Assert.assertEquals(serverType /* actual value */, "nginx/1.17.10 (Ubuntu)" /* expected value */);
In the above code, we verify the actual value of each header viz., Content-Type, Server, and Content-Encoding with the expected value. Shown below is the screenshot of the test result.
This output indicates that the actual value and expected value of each header matches.
Now suppose we provide the expected value of Content-Type to "application/XML" in the above code as below.
Assert.assertEquals(contentType /* actual value /, "application/xml" / expected value */);
If we run the same test above after the change, we will get the following output.
As we can see, the difference in actual value "application/JSON" and the expected value "application/XML" raises an assertion error.
In this way by using the header-specific methods of Response interface, we can validate the Response Header in Rest API.
Note: The video tutorial on the same topic can be found at Verify Response Headers in Rest Assured.
Key TakeAways
In this article, we discussed the ways to validate the response header in REST using Rest Assured.
- Every response obtained from the server may contain zero or more headers that provide metadata about the response.
- Rest Assured provides various methods using which we can read individual or all headers sent by the server.
- headers() and getHeaders() return a collection of headers that is iterable for individual entries. The method "getHeader()" and header() returns individual header whose name is specified as an argument.
- Using these methods and the TestNG Assert we can then validate the headers by comparing the actual and expected values of each header.
After validating the status and headers of the response, we will move on to reading the actual Response body in the next article.
Read JSON Response Body using Rest Assured
In the last two tutorials we have learnt about Response Status Code, Status line and Headers. We will continue with the same example in those tutorials and verify the body if the Response. If you have not gone through the first two tutorials then I would suggest you go through these links
- First Test with Rest-Assured
- Validate Response Status code and Status Line using Rest-Assured
- Validate Response Header using Rest-Assured
In this tutorial, we will learn about How to Read JSON Response Body using Rest Assured? and How to Validate Content of a Response Body?
Read JSON Response Body using Rest Assured
Let us continue with the example of Weather web service that we used in the previous tutorials. When we request for the Weather details of a particular city, Server responds by sending the Weather details of the city as the Response Body. Response interface contains two methods to get the Response Body
- Response.body() : returns ResponseBody
- Response.getBody() : returns ResponseBody
Using these methods we can get an Object of type io.restassured.response.ResponseBody. This class represents the Body of a received Response. Using this class you can get and validate complete or parts of the Response Body. In the below code we will simply read the complete Response Body by using Response.getBody() and will print it out on the console window.
@Test
public void WeatherMessageBody()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("/Hyderabad");
// Retrieve the body of the Response
ResponseBody body = response.getBody();
// By using the ResponseBody.asString() method, we can convert the body
// into the string representation.
System.out.println("Response Body is: " + body.asString());
}
ResponseBody interface also has a method called .asString(), as used in the above code, which converts a ResponseBody into its String representation. If you run this test the output will look something like this:
Note: Response.body() method does exactly the same thing. So you can even use .body() method in the above code.
How to Validate Response Body contains some String?
ResponseBody can return the response body in a String format. We can use simple String methods to verify certain basic level of values in the Response. For e.g. we can use the String.contains() method to see if the Response contains a "Hyderabad" in it. The below code shows how to check for sub string presence.
@Test
public void WeatherMessageBody()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("/Hyderabad");
// Retrieve the body of the Response
ResponseBody body = response.getBody();
// To check for sub string presence get the Response body as a String.
// Do a String.contains
String bodyAsString = body.asString();
Assert.assertEquals(bodyAsString.contains("Hyderabad") /*Expected value*/, true /*Actual Value*/, "Response body contains Hyderabad");
}
Check String presence by ignoring alphabet casing
We can also ignore the casing using the String internal methods. To do this we will convert the Response in lower case and then compare it with our lower case string value. Below code demonstrates that.
@Test
public void WeatherMessageBody()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("/Hyderabad");
// Retrieve the body of the Response
ResponseBody body = response.getBody();
// To check for sub string presence get the Response body as a String.
// Do a String.contains
String bodyAsString = body.asString();
// convert the body into lower case and then do a comparison to ignore casing.
Assert.assertEquals(bodyAsString.toLowerCase().contains("hyderabad") /*Expected value*/, true /*Actual Value*/, "Response body contains Hyderabad");
}
The above two approaches suffer from a classical problem, what if the string "Hyderabad" is present in a wrong node or may be multiple instances of the same string are present. This is not a fool proof way of testing a particular node in the Response. There are better ways, Response interface gives you a mechanism to extract nodes based on a given JsonPath. There is a method called Response.JsonPath(), which returns a io.restassured.path.json.JsonPath Object. This object can be used to further query specific parts of the Response Json.
If you are not aware of JsonPath, please go through these tutorials
How to Extract a Node text from Response using JsonPath?
Let us continue with the above example and retrieve the City from the Response. To do so, we will simply get the JsonPath object from the Response interface and then query for the particular node. Just to be very clear, let us look at the Weather API response again.
{
"City": "Hyderabad",
"Temperature": "25.51 Degree celsius",
"Humidity": "94 Percent",
"Weather Description": "mist",
"Wind Speed": "1 Km per hour",
"Wind Direction degree": " Degree"
}
In this response, if we want to go to the City node, all we have to do is have the following JsonPath: $.City. Try it out on the JsonPath Evaluator to verify the output.
Now let us look at the code, pay specific attention to the comments in the code.
Note: In Java JsonPath you do not need to have $ as the root node. You can completely skip that.
@Test
public void VerifyCityInJsonResponse()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("/Hyderabad");
// First get the JsonPath object instance from the Response interface
JsonPath jsonPathEvaluator = response.jsonPath();
// Then simply query the JsonPath object to get a String value of the node
// specified by JsonPath: City (Note: You should not put $. in the Java code)
String city = jsonPathEvaluator.get("City");
// Let us print the city variable to see what we got
System.out.println("City received from Response " + city);
// Validate the response
Assert.assertEquals(city, "Hyderabad", "Correct city name received in the Response");
}
The output of the code passes the assertion and it also prints the City name retrieved from the Response. As shown in the image below
On the similar lines, you can extract any part of the Json response using the JsonPath implementation of Rest-Assured. This is very convenient, compact and easy way to write tests.
Sample Code to read all the nodes from Weather API Response
Now that we know how to read a node using JsonPath, here is a small piece of code that reads all the nodes and prints them to the Console.
@Test
public void DisplayAllNodesInWeatherAPI()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/weather/city";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("/Hyderabad");
// First get the JsonPath object instance from the Response interface
JsonPath jsonPathEvaluator = response.jsonPath();
// Let us print the city variable to see what we got
System.out.println("City received from Response " + jsonPathEvaluator.get("City"));
// Print the temperature node
System.out.println("Temperature received from Response " + jsonPathEvaluator.get("Temperature"));
// Print the humidity node
System.out.println("Humidity received from Response " + jsonPathEvaluator.get("Humidity"));
// Print weather description
System.out.println("Weather description received from Response " + jsonPathEvaluator.get("Weather"));
// Print Wind Speed
System.out.println("City received from Response " + jsonPathEvaluator.get("WindSpeed"));
// Print Wind Direction Degree
System.out.println("City received from Response " + jsonPathEvaluator.get("WindDirectionDegree"));
}
In the next chapter, we will study about XmlPath usage on XML Responses.
Working with Query Parameters in Rest Assured | REST API
So far in this series, we have covered reading the response body. But as you proceed ahead, a simple GET or a POST request won't suffice to the complexity your test scenario might have. You may need to test the API for different query parameters. In this article, we will cover how query parameters are passed in the HTTP request using rest assured with the following main sections-
- What is the composition of the URL?
- What are Query Parameters?
- How to send a request using Query Parameters in Rest Assured?
What is the composition of the URL?
Similar to the examples used in the previous articles we will be using the Bookstore API in this article as well. Consider the below sample URL to fetch details corresponding to a book-
https://demoqa.com/BookStore/v1/Book?ISBN=9781449325862 Though the above URL looks very basic, it still let us understand its different components.
- https:// - It is a protocol that ensures secure connection between the web server and the web browser.
- https://demoqa.com/swagger/#/BookStore - It is the domain name that hosts the website. It generally ends with .com, .in, .net, etc.
- BookStore/v1/Book - This is the path or the URI that identifies the resource applied by the request.
- ?ISBN=9781449325862 - This is a string query parameter. The question mark denotes the start of a query string parameter. There can be one or more query parameters in the URL.
Let us now understand more about query parameters in the next section.
What are Query String Parameters?
You might not always want to fetch all the results corresponding to a request. There may be scenarios where you need to fetch only a few or a single record. In such cases, query string parameters play an important role. These are appended at the end of the URL after using a '?'. Try entering the example URL in the browser address bar and observe the results available under Network -> Payload-
The query parameter, i.e., ISBN=9781449325862, is displayed under the Query String Parameters field. In a similar way, if there are multiple query parameters passed in the URL, you will see all of them in this field. As an exercise, you may search some keywords on Google search and see the query parameters that get appended to the URL. Observe the search keyword is displayed after 'q=your search keyword'. Additionally, you will observe other query parameters which are separated from each other through an '&' sign.
These signs are termed URL parameters and are beyond the scope of our discussion here.
Now that we have understood what query parameters are and how they are used in requests, we can proceed to see how we can send requests with Query Parameters in Rest Assured.
How to send a request using Query Parameters in Rest Assured?
In this section, we will see how we can automate our test case by passing query parameters in Rest assured. We will simply use the URL containing the query parameter as discussed in sections above to show how we can send a request using query parameters in rest assured. Let us see how the code would look like and then we will walkthrough it step by step.
package bookstore;
import org.junit.Test;
import io.restassured.RestAssured;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification;
public class QueryParam {
@Test
public void queryParameter() {
//Defining the base URI
RestAssured.baseURI= "https://bookstore.toolsqa.com/BookStore/v1";
RequestSpecification httpRequest = RestAssured.given();
//Passing the resource details
Response res = httpRequest.queryParam("ISBN","9781449325862").get("/Book");
//Retrieving the response body using getBody() method
ResponseBody body = res.body();
//Converting the response body to string object
String rbdy = body.asString();
//Creating object of JsonPath and passing the string response body as parameter
JsonPath jpath = new JsonPath(rbdy);
//Storing publisher name in a string variable
String title = jpath.getString("title");
System.out.println("The book title is - "+title);
}
}
RestAssured.baseURI= "https://bookstore.toolsqa.com/BookStore/v1";
RequestSpecification httpRequest = RestAssured.given();
We first define the base URI to create a request to the service endpoint.
Response res = httpRequest.queryParam("ISBN","9781449325862").get("/Book");
Next we send the resource details like the book ISBN as a query parameter to search in books using the GET request.
Note: If you need to send multiple query parameters you simply need to append queryParam() method with the parameter name and value to the RequestSpecification object, i.e. httpRequest in the above case.
ResponseBody body = res.body();
String rbdy = body.asString();
JsonPath jpath = new JsonPath(rbdy);
String title = jpath.getString("title");
System.out.println("The book title is - "+title);
Finally, we are storing the response body as a String object and parsing its different values using the JSONPath object. We then fetch the title from the response body.
Note: You may read more about JSONPath in our elaborate article on the same.
And that's it! See how easily we could send the query parameters to our request by simply using a queryParam() method. The above code execution displays the title of the book with respect to the ISBN.
You can now go ahead with automating your test scripts with request having query parameter(s) and enhance your rest assured code.
Key Takeaways
- We have seen the different components of a URL and the purpose of each component.
- We also understood what are query parameters and how they work when we access a URL in the browser.
- Query parameters passed to the rest assured tests using the queryParam() method accepts the parameter name and value.
- You may use the queryParam() method not just once, but as many times as the number of query parameters in your GET request.
Understanding HTTP POST Request Method using Rest Assured
In our discussions of HTTP REST Methods, we went through the GET request in our earlier tutorials. We have also discussed validations like validating headers and the status of the response obtained from the server. In this article, we'll discuss the next method in REST API i.e. the POST request using Rest Assured library. We'll cover the following topics in this article.
- What is an HTTP POST request method?
- How to use POST request method using Rest Assured?
- Create a Request pointing to the Service Endpoint.
- Create a JSON request which contains all the fields.
- Add JSON body in the request and send the Request.
- Validate the Response.
- Changing the HTTP Method on a POST Request using Rest Assured.
What is an HTTP Post Request method?
We use the verb "Post" everywhere when we are dealing with the web. For example, when we are submitting any registration form on a particular webpage like Gmail. We provide the required data and click submit. So through this action of submitting data, we are actually POSTING or sending the data to the server. The verb "POST" in HTTP, also called as POST request method in HTTP sends data to the server. In our example, we are sending our names, email and decided password as data.
Now let's go to Swagger UI of API https://demoqa.com/BookStore/v1/Books Next, let's create a new user using POST request as shown in the following screenshot.
In the above example, we have provided all the details for the new user including id and ISBN. When we "execute" requests we initiate a POST request and send all the information to the server. On the server side, the new user will be created with all the provided information and a response will be sent back to the client. Look at the following screenshot.
Have a look at the "curl" command that has a POST request with all the data. The operation is successful and hence we get the response code as 201.
The content-type parameter is "application/JSON" in the above example. The data sent to the server by POST request can also be XML(content-type: application/XML). This data is in the body of the HTTP request. As we learned from the above example, the POST method usually creates a new record in the application database. Although, it is not always true. Sometimes in creating web applications we use POST request redirections to ensure that no one hits that URL directly without proper navigation channels. For example, you must have noticed that refreshing or pressing "back" on payment pages does not work directly. It is secure as the data being sent is not visible in the URL string. The data can also be encrypted using HTTPS (HTTP Secure) to enhance security.
As far as real-world applications are concerned, the POST request can have a very big size and also a complex body structure. Since it provides an extra layer of security, we often use it for passing sensitive business-related data. It is also noteworthy here that the POST request does not always require all the data to be filled by the user. It completely depends on the server implementation and therefore you should always go through the documentation before.
Let us now use Rest Assured to simulate an HTTP POST request.
How to use POST Request Method using Rest Assured?
In Rest Assured, we make use of the post() method to make an HTTP POST request.
For making an HTTP Post request using Rest Assured, let's add a simple JSON library in our classpath so that we can create JSON objects in the code. We can use the following URL to download simple JSON from the Maven: https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple. Once the jar is downloaded we can add it to the classpath.
Following are the steps we'll follow to make a POST Request using Rest Assured.
- Create a Request pointing to the service Endpoint.
- Create a JSON Request which contains all the fields.
- Add JSON body in the request and send the request.
- Validate the Request.
- Changing the HTTP Method on a POST Request.
Let us now perform each step one by one.
Create a Request pointing to the Service Endpoint
We can start coding once the JSON jar downloads.
Consider following code lines.
RestAssured.baseURI = "https://demoqa.com/BookStore/v1/Books";
RequestSpecification request = RestAssured.given();
In the above code, we initialize a base URI with a link to the bookstore and the 'createUser' API. Next, we create a 'request' using RequestSpecification.
Note: Above code is self-explanatory. In case of any problems, please refer to previous tutorials.
Create a JSON request which contains all the fields
Next, we will create a JSON request object that will contain the data we need to create a new user. Given below is the code for the same:
// JSONObject is a class that represents a Simple JSON.
// We can add Key - Value pairs using the put method
JSONObject requestParams = new JSONObject();
requestParams.put("userId", "TQ123");
requestParams.put("isbn", "9781449325862");
In the above code, we have created a JSONObject variable (JSONObject belongs to org.json.simple package). Here we provide a JSON String containing the data to post to the server. Here we have the requestParams object above for our test web service with multiple nodes in the JSON. Each node is added to the JSON string using the JSONObject.put(String, String) method. Once all the nodes are added in this manner, we get the String representation of JSONObject by calling JSONObject.toJSONString() method.
Add JSON body in the request and send the Request
Now that we have created the JSON string with the required data, the next step will be to add this JSON to the request body and send or post the request. Look at the following code:
// Add a header stating the Request body is a JSON
request.header("Content-Type", "application/json"); // Add the Json to the body of the request
request.body(requestParams.toJSONString()); // Post the request and check the response
So in this step, we simply add the JSON String to the body of the HTTP Request and set the Content-Type header field value to application/JSON. Next, we use the method RequestSpecification.body(JsonString) to put the JSON body into the request. Using this method we can update the content of the HTTP Request Body.
Next using the post () method on the request object we send this data to the server using the 'BookStoreV1BooksPost' API.
Note: Calling the RequestSpecification.body method multiple times updates the body each time to the latest JSON String.
Validate the Response
After posting the request we have to validate the response we received from the server as a result of a POST request. Given below is the code for validating the response:
Response response = request.post("/BookStoreV1BooksPost");
System.out.println("The status received: " + response.statusLine());
Here we read the status obtained using the statusLine() method and print it to the console.
Changing the HTTP Method on a POST Request using Rest Assured
So what happens when we change the HTTP Request method on a POST request? For example what happens when instead of the expected POST we send the GET? Let's discuss this scenario.
Following is the code wherein we have sent a GET request to an Endpoint when it actually expects POST.
public void UserRegistrationSuccessful()
{
RestAssured.baseURI ="https://demoqa.com/Account/v1";
RequestSpecification request = RestAssured.given();
JSONObject requestParams = new JSONObject();
requestParams.put("userName", "test_rest");
requestParams.put("password", "Testrest@123");
request.body(requestParams.toJSONString());
Response response = request.put("/User");
ResponseBody body = response.getBody();
System.out.println(response.getStatusLine());
System.out.println(body.asString());
}
When we execute the above code, we get the following response.
We can clearly see the output says the incorrect usage of the HTTP Request Method. Similarly, we have other negative scenarios listed below which we will leave to users to try themselves.
- Sending incomplete POST Data.
- Try Sending JSON data with incorrect syntax.
- Sending incorrect HTTP request method in the Request.
You can try the above scenarios on the same URL used above to demonstrate the POST request.
Note: Corresponding Postman tutorial for Post request can be found at Response in Postman.
The video tutorial for this topic is available at What is Post Request?
Key TakeAways
In this article, we discussed the HTTP POST request method using Rest Assured.
- Post request is used to send or post the data to the server.
- Post request mostly results in creating a new record in the database. It can also update the existing record in the database.
- Rest Assured uses a post () method to make HTTP POST requests.
- We usually send the JSON data along with the request object and then POST it to the server.
In the next tutorial, we'll cover the Deserialization technique to convert the JSON Response body into a Class instance.
Serialization and Deserialization in Java
Serialization and Deserialization in Java is an important programming concept. It is applicable to all major programming languages. In this chapter, we will try to understand this concept in the context of Java language. At the end of this chapter, we will be able to
- Conceptually know meaning of Serialization and Deserialization
- Understand the Java code to Serialization and Deserialization
Let us first define the two terms in Generic form, irrespective of any programming language.
What is Serialization?
Serialization is a process where you convert an Instance of a Class (Object of a class) into a Byte Stream. This Byte Stream can then be stored as a file on the disk or can also be sent to another computer via the network. Serialization can also be used to save the sate of Object when the program shuts down or hibernates. Once the state is saved on disk using Serialization, we can restore the state by DeSerializing the class from disk.
Let us try to visualize this using a small diagram. In this diagram, we will create a small class called Rectangle, which represents a real-life rectangle. Here is the code for the class
public class Rectangle {
private double height;
private double width;
public Rectangle(double height, double width)
{
this.height = height;
this.width = width;
}
public double Area()
{
return height * width;
}
public double Perimeter()
{
return 2 * (height + width);
}
}
Note: This class is not yet Serializable as per Java standards, let us ignore it for the time being.
Serialization process on the Rectangle class will look like this.
The encoding scheme that is used to convert from an Object of Class Rectangle to Byte stream is governed by the Serialization encoding standards mentioned here.
Serializable Interface
In Java, a Serializable object is an object which inherits from either of the two interfaces
Serializable interface is a marker interface. Which means that you do not have to implement any methods if your class derives from this interface. This is just a marker and the Java runtime, when trying to Serialize the class, will just check for the presence of this interface in the class. If Serializable interface is present in the class inheritance hierarchy, Java run time will take care of Serialization of the class.
On the other hand, the Externalizable interface is not a marker interface. If you derive from Externalizable interface you have to implement these two methods
- readExternal(ObjectInput input)
- writeExternal(ObjectOutput output)
We should inherit from Externalizable interface only when we want to overtake the Java's default serialization mechanism. If you want to use the default Java's serialization mechanism than you should inherit from Serializable interface only.
With this understanding, our Rectangle class will now inherit from Serializable interface.
public class Rectangle implements Serializable{
private double height;
private double width;
public Rectangle(double height, double width)
{
this.height = height;
this.width = width;
}
public double Area()
{
return height * width;
}
public double Perimeter()
{
return 2 * (height + width);
}
}
Serializing an Object in Java
Let us quickly take a look at the Serialization process in Java. In this process, we will perform these four steps
- We will create a new FileOutputStream of a file where we want to serialize the class
- We will then ObjectOutputStream on the FileOutputStream created in step 1
- We will then write the object into the ObjectOutputStream
- Finally, we will close all the stream objects to save properly write and terminate all streams.
Below is the code to perform the task. Pay attention to the comments that are mentioned above every line of code.
public static void SerializeToFile(Object classObject, String fileName)
{
try {
// Step 1: Open a file output stream to create a file object on disk.
// This file object will be used to write the serialized bytes of an object
FileOutputStream fileStream = new FileOutputStream(fileName);
// Step 2: Create a ObjectOutputStream, this class takes a files stream.
// This class is responsible for converting the Object of any type into
// a byte stream
ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
// Step 3: ObjectOutputStream.writeObject method takes an Object and
// converts it into a ByteStream. Then it writes the Byte stream into
// the file using the File stream that we created in step 1.
objectStream.writeObject(classObject);
// Step 4: Gracefully close the streams
objectStream.close();
fileStream.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args)
{
Rectangle rect = new Rectangle(18, 78);
SerializeToFile(rect, "rectSerialized");
}
When we run this program, it creates a file named "rectSerialized" in the root folder of the project. Just browse to that location and try to open the file using a notepad. Below image shows that
When you open this file you can see that it contains garbled characters. The contents are encoded and are not in human readable format. As shown in the image below.
This shows what exactly serialization means and how we can serialize an object in Java. This should also give you a practical understanding of the different steps involved in serialization process including the input and output results of the different steps.
Deserializing to an Object in Java
In the previous section, we learnt the Serialization converts a class instance into a byte stream which is then stored in a file on disk. Let us quickly take a look at the Deserialization process in Java, which is opposite of Serialization. In this process, we will read the Serialized byte stream from the file and convert it back into the Class instance representation. Here are the steps that we will follow.
- We will create a new FileInputStream to read the file which contains the serialized byte stream of the target class. Rectangle class in our case.
- We will then create an ObjectInputStream on the FileInputStream created in step 1
- We will then read the object using ObjectInputStream and store it in a variable of type Rectangle.
- Finally, we will close all the stream objects to save properly write and terminate all streams.
Below is the code to perform the task. Pay attention to the comments that are mentioned above every line of code.
public static Object DeSerializeFromFileToObject(String fileName)
{
try {
// Step 1: Create a file input stream to read the serialized content
// of rectangle class from the file
FileInputStream fileStream = new FileInputStream(new File(fileName));
// Step 2: Create an object stream from the file stream. So that the content
// of the file is converted to the Rectangle Object instance
ObjectInputStream objectStream = new ObjectInputStream(fileStream);
// Step 3: Read the content of the stream and convert it into object
Object deserializeObject = objectStream.readObject();
// Step 4: Close all the resources
objectStream.close();
fileStream.close();
// return the deserialized object
return deserializeObject;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void main(String[] args)
{
Rectangle rect = new Rectangle(18, 78);
SerializeToFile(rect, "rectSerialized");
Rectangle deSerializedRect = (Rectangle) DeSerializeFromFileToObject("rectSerialized");
System.out.println("Rect area is " + deSerializedRect.Area());
}
Just to verify that the original state of the Rectangle class is restored, we will debug the code and inspect deSerializedRect variable. The below image shows that the original state of (Height: 18 and Width: 78) Rectangle class is restored.
I hope that this tutorial clears the basic concept of Serialization and Deserialization in Java for you.
Deserialize JSON Response using Rest Assured
JSON is an extremely popular format when it comes to APIs. Almost all of the APIs either transfer data in the XML format or JSON format of which JSON is a popular one. Not only through APIs, but the companies also use JSON to transfer data between their own server to UI because of its lightweight and easily readable features. As a professional tester or a developer, it will be rare if you do not come across a JSON file and verify its legibility. To work with them, you should know how to read them through code and make use of automation testing to accomplish your tasks easily. In this article, we'll discuss the same concept used in the body of the REST Response. This technique to read the JSON Response is also termed as to "Deserialize" the JSON Response.
Serialization and Deserialization are programming techniques where we convert Objects to Byte Streams and from Byte Streams back to Objects respectively. We have already discussed this technique in the article Serialization and Deserialization in Java. We will now cover the following topics in this article:
- What is Serializing a JSON?
- What is Deserializing of a JSON Response?
- How to Deserialize JSON Response to Class with Rest Assured?'
- How to Deserialize JSON Response Body based on Response Status?
What is Serializing a JSON?
Serialization, as mentioned above, is a process of converting data objects into a stream of data. The reverse process of serialization (stream to object) is deserialization. Both the processes are platform-independent. This means we can serialize an object on a Windows platform and deserialize it on macOS.
As far as rest assured is concerned, we are aware that the data exchange between client and server takes place in JSON format by REST web service. The stream of data here is JSON data.
For example, if we have the following object:
{tools: [1, 3, 7, 9], api: "QA"}
when the above object is serialized into JSON, the output will look like the one shown below:
{
"tools":[1, 3, 7, 9],
"api":"QA"
}
The above data string can be stored or transferred anywhere. The recipient will then deserialize the data back to its original format ie object.
Java POJO classes are one way we can serialize the objects and use them in Rest Assured. The details of implementation are beyond the scope of this tutorial. Serialization is discussed in our previous tutorial Serialization and Deserialization in Java. While working in the testing industry, we will be more concerned about deserialization. The same is discussed in the next section in detail.
What is Deserializing a JSON Response?
Before going ahead, it is recommended to read the article REST Api testing in Rest-Assured and converse yourself with REST, API testing, REST Assured and other similar concepts.
In REST APIs, the process of deserialization is performed especially for the JSON responses we receive from the API. So, when we say we are deserializing the JSON, this means we convert the JSON format into a type we prefer, most frequently POJO (Plain Old Java Object) classes.
So in this process, we essentially tell our code to use strongly typed strings that are less error-prone instead of JSON strings. Let's go ahead and see practically how to deserialize the JSON responses to class with Rest Assured.
How to Deserialize JSON Response to Class with Rest Assured?
We will continue from our example in Making a POST request using Rest-Assured to implement deserialization of JSON responses. In this article, we have seen that a successful post request sends the following response:
{
"SuccessCode": "OPERATION_SUCCESS",
"Message": "Operation completed successfully"
}
In the POST request example, we have used JSONPath to validate the response body parts. Now we have to convert this response body to a Java class (POJO). In other words, we'll convert the JSON which is a string form to a class form i.e. deserialize JSON.
Note: Deserialization is also called Object Representation of structured data. The structured data here is JSON.
So how do we begin with deserialization?
First and foremost we need to create a class that has all the nodes (or key) of the JSON response. As we are already aware, the Success response has two nodes:
- SuccessCode
- Message
Both these nodes contain String values. The screenshot below shows the part of the response we receive.
So we have to create a class with two string variables that will represent nodes in the JSON. Given below is the class code for the same.
@Test
public class JSONSuccessResponse {
// Note: The name should be exactly as the JSON node name
// Variable SuccessCode will contain value of SuccessCode node
public String SuccessCode;
// Variable Message will contain the value of Message node
public String Message;
}
Now that we have a JSONSuccessResponse class to store all the nodes present in Successful Response, let's understand how to use Rest-Assured to automatically convert JSON Response Body to the instance of the JSONSuccessResponse class.
Converting JSON Response Body to JSONSuccessResponse class
The class io.restassured.response.ResponseBody that represents the response in Rest Assured, has a method called .as(Class<T>). Here, Class<T>
is a generic form of any class of type T which is also referred to as template class. In this case, Class <T>
will be JSONSuccessResponse.
Internally, the "as()" method performs two actions:
- Create an instance of JSONSuccessResponse class.
- Copy the JSON node variables to the respective instance variables of the class. For example, the value of SuccessCode node to the variable SucessCode and value of Message to variable Message.
Given below is the code demonstrating the Response.Body.as(Class<T>
) method.
@Test
public void UserRegistrationSuccessful() {
RestAssured.baseURI ="https://demoqa.com";
RequestSpecification request = RestAssured.given();
JSONObject requestParams = new JSONObject();
requestParams.put("UserName", "test_rest");
requestParams.put("Password", "rest@123");
request.body(requestParams.toJSONString());
Response response = request.post("/Account/v1/User");
ResponseBody body = response.getBody();
// Deserialize the Response body into JSONSuccessResponse
JSONSuccessResponse responseBody = body.as(JSONSuccessResponse.class);
// Use the JSONSuccessResponseclass instance to Assert the values of Response.
Assert.assertEquals("OPERATION_SUCCESS", responseBody.SuccessCode);
Assert.assertEquals("Operation completed successfully", responseBody.Message); }
Once we get the value in responseBody variable, we can validate the response as shown in the code below.
// Use the RegistrationSuccessResponse class instance to Assert the values of
// Response.
Assert.assertEquals("OPERATION_SUCCESS", responseBody.SuccessCode);
Assert.assertEquals("Operation completed successfully", responseBody.Message);
In this way, we can apply assertions to validate the response or even pass this response as input to other tests.
How to Deserialize JSON Response Body based on Response Status?
In the previous section, we discussed how to deserialize the JSON in case of a successful response. But in real-time applications, we can also receive responses that are failures. In such a case, Rest API may return a completely different response body. One such format of failed response may be as shown below:
{
"FaultId": "User already exists",
"fault": "FAULT_USER_ALREADY_EXISTS"
}
If we use the class JSONSuccessResponse to deserialize the above response, it will not work. This is because Rest Assured will not find the nodes SuccessCode and Message in the response body like in the above section. These two variables in the class will have values as null.
The solution to this is to maintain another class that will be used for deserializing the failure response. This class will have the following structure.
public class JSONFailureResponse {
String FaultId;
String fault;
}
But now that we have two classes, one for a successful response and another for failure, how can we handle both these in an application?
We can do this using the HTTP Status Code returned by the server. Rest API in this series returns Status Code = 201 in case of success and 200 in case of failure.
So by making use of the status code we can deserialize the response into appropriate POJO classes depending on success or failure. Below given code is an updated version of the above code and it takes care of success as well as failure response.
@Test
public void UserRegistrationSuccessful() {
RestAssured.baseURI ="https://demoqa.com";
RequestSpecification request = RestAssured.given();
JSONObject requestParams = new JSONObject();
requestParams.put("UserName", "test_rest");
requestParams.put("Password", "rest@123");
request.body(requestParams.toJSONString());
Response response = request.post("/Account/v1/User");
ResponseBody body = response.getBody();
System.out.println(response.body().asString());
if(response.statusCode() == 200) {
// Deserialize the Response body into JSONFailureResponse
JSONFailureResponse responseBody = body.as(JSONFailureResponse.class);
// Use the JSONFailureResponse class instance to Assert the values of Response.
Assert.assertEquals("User already exists", responseBody.FaultId);
Assert.assertEquals("FAULT_USER_ALREADY_EXISTS", responseBody.fault);
} else if (response.statusCode() == 201) {
// Deserialize the Response body into JSONSuccessResponse
JSONSuccessResponse responseBody = body.as(JSONSuccessResponse.class);
// Use the JSONSuccessResponse class instance to Assert the values of response.
Assert.assertEquals("OPERATION_SUCCESS", responseBody.SuccessCode);
Assert.assertEquals("Operation completed successfully", responseBody.Message);
}
}
In the above code, we deserialize the response to JSONSuccessResponse or JSONFailureResponse class depending on whether the Status code is Success or Failure.
Note: Another way to deserialize multiple responses is by using the inheritance chain which readers can implement as an exercise.
Key TakeAways
In this article, we have discussed the deserialization of JSON responses into a POJO class.
- Deserialization is the process of converting JSON responses into classes. By doing this we can convert JSON strings into strongly types strings that are less error-prone.
- For deserializing the responses, we create a separate class that has the same variables that are present in the JSON response like StatusCode and Message.
- In the code, we can use this class object to read the JSON response as shown in the example above.
- Deserialization of multiple responses like success and failure depends on the status code. For this, we need two POJO classes one each for success and failure. Depending on the value of the Status code we can call instances of the appropriate class and deserialize the response.
In our next article, we will discuss authentication and authorization in Rest.
Authentication and Authorization in REST WebServices
Authentication and Authorization in REST WebServices are two very important concepts in the context of REST API. The majority of the time you will be hitting REST API's which are secured. By secure, we mean that the APIs which require you to provide identification. Identification can be provided in the form of
- Username and a Password
- Authentication tokens
- Secret keys
- Bio-metrics and many other ways
In the context of REST API, we will be more interested in the first three options. The Authentication and Authorization models that we will discuss are spread across multiple tutorials, starting from this tutorial.
You may also go through the recording of the Postman Tutorial where our experts have explained this concepts in depth.
What is Authentication? and How does Authorization work in REST WebServices?
Authentication is a process to prove that you are the person you intend to be.
For e.g. while logging into your email account, you prove that you are you by providing a Username and a Password. If you have the Username and the Password you are who you profess to be. This is what Authentication means.
In the context of REST API authentication happens using the HTTP Request.
Note: Not just REST API, authentication on any application working via HTTP Protocol happens using the HTTP Request.
Basic Authentication Flow
Taking the example of email login, we know that in order to Authenticate our self we have to provide a username and a password. In a very basic Authentication flow using Username and Password, we will do the same thing in REST API call as well. but how do we send the Username and Password in the REST request?
A REST request can have a special header called Authorization Header, this header can contain the credentials (username and password) in some form. Once a request with Authorization Header is received, the server can validate the credentials and can let you access the private resources.
Note: I hope from previous tutorials you are able to understand the meaning of a Resource. If not, please go through this tutorial: Rest architectural elements. A private resource is one that is not accessible to everyone. You need to Authenticate yourself to access the private resource. For e.g. the email inbox, you have to log in to see the emails.
Let us see it with an example, we have created an API that needs a valid Username and Password to access the Resource.
Endpoint: http://restapi.demoqa.com/authentication/CheckForAuthentication
In the code below we will try to hit the URL and see what is the Response that we get.
@Test
public void AuthenticationBasics()
{
RestAssured.baseURI = "https://restapi.demoqa.com/authentication/CheckForAuthentication";
RequestSpecification request = RestAssured.given();
Response response = request.get();
System.out.println("Status code: " + response.getStatusCode());
System.out.println("Status message " + response.body().asString());
}
In the code above we are simply making an HTTP GET request to the endpoint. In this code, we have not added any Authorization header. So the expected behavior is that we will get Authorization error. If you run this test, you will get the following output.
Status code: 401
Status message:
{
"StatusID": "FAULT_USER_INVALID_USER_PASSWORD",
"Status": "Invalid or expired Authentication key provided"
}
The output clearly says that we have "Invalid or expired Authentication key provided" error. This means that either there was no Authentication information or the information supplied was invalid. Eventually, the server denies our request and returns an error response.
Note: Pay special attention to the Status code returned. In case of , Authentication failures Server should respond with a status code of 401 Unauthorized.
Try to hit that URL using a browser. You should get a Username and Password prompt. The below image shows what you should be getting when you hit this URL from the browser.
In this tutorial, we will not discuss how to pass Authentication information in the Request header. Here we will only focus on the definitions of Authentication and Authorization. In the next set of tutorials, we will see different Authentication models, which will solve the above problem.
What is Authorization? and How does Authorization work in REST WebServices?
Authorization is the process of giving access to someone. If you are Authorized then you have access to that resource. Now to Authorize you to need to present credentials and as we discussed earlier that process is called Authentication. Hence Authorization and Authentication are closely related terms and often used interchangeably.
Before ending the tutorial let us see the contents of the private resource in the URL mentioned above. To do that enter the following credentials
-
Username: ToolsQA
-
Password: TestPassword
The server will be able to Authenticate and then Authorize you to access the private resource content. The below image shows the content after successful Authentication.
With this basic understanding of Authentication and Authorization, read the coming tutorials where we will discuss the specif types of Authentication models in REST API.
Basic Authentication
In our series, we have so far covered the basics of Rest Assured, the different types of requests like POST, PUT and DELETE. In addition, we also covered the basics of Authentication & Authorization concepts of Rest API. Practically in the projects, as we proceed with automation, we come across complex APIs. These require basic authentication implementation mixed along with other code. In this article, we will cover the handling of basic authentication in Rest Assured. We will focus on the below points-
- What is Basic Authentication(Auth)?
- What are the different authentication schemes provided by Rest Assured?
- Basic Authentication in Rest Assured.
- Preemptive Authentication.
- Digest Authentication.
- Form Authentication.
- OAuth Authentication.
- OAuth1
- OAuth2
What is Basic Authentication (Auth)?
While going through the previous tutorials you must have noticed that we have used the username and the password (authentication credentials) for certain APIs. This credential setting is to enforce access control for the web resources and is generally passed in the header field of an HTTP request. The implementation of basic authentication is to ensure that the APIs are secured and only the users who are authorized have the access to view them. Hence, the authentication information is not encrypted or hashed but encoded as base-64. We will now see the different schemes used in Rest Assured for authentication and you may go through our previous article on Authentication and Authorization for more information.
What are the different authentication schemes provided by Rest Assured?
To test and validate any secured API, you will have to use some authentication scheme. Rest Assured provides several authentication schemes which we are going to discuss in this part.
Before proceeding to understand the use of authentication in Rest Assured, let us execute our Rest Assured test without using any sort of authentication. Below is the code for your reference-
package org.example;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification;
public class BasicAuth {
@Test
public void getData() {
RequestSpecification httpRequest = RestAssured.given();
Response res = httpRequest.get("https://postman-echo.com/basic-auth");
ResponseBody body = res.body();
//Converting the response body to string
String rbdy = body.asString();
System.out.println("Data from the GET API- "+rbdy);
}
}
The code is pretty simple and uses the get () method to send requests to the server. Do not worry if you don't understand. It will be explained in the later examples. On executing this code the result would be-
Observe the message in the first line - "Data from the GET API - Unauthorized". This is the issue that we are going to fix using the basic authentication in our rest assured tests. Let us quickly jump on to understanding the same.
Basic Authentication in Rest Assured
As discussed above, the basic authentication scheme uses the username and password in base64 encoded format. The request header needs to contain the credentials of the user for access to the resource. It is very easy to send the credentials using the basic auth and you may use the below syntax-
given().auth().basic("your username", "your password").get("your end point URL");
In the given method you need to append the method of authentication specification followed by the basic HTTP auth where you will pass the credentials as the parameters. Another type of basic authentication is preemptive which we will discuss next.
package org.example;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification;
public class BasicAuth {
@Test
public void getData() {
RequestSpecification httpRequest = RestAssured.given().auth().basic("postman", "password");
Response res = httpRequest.get("https://postman-echo.com/basic-auth");
ResponseBody body = res.body();
//Converting the response body to string
String rbdy = body.asString();
System.out.println("Data from the GET API- "+rbdy);
}
}
Preemptive Authentication
By default, Rest Assured uses the challenge-response mechanism. This means that it waits for the server to challenge rather than send the credentials directly. By using the preemptive directives we can avoid that additional call that the server makes and hence additional complications. In a way, it is similar to the basic auth we saw above, the only difference is that an additional premptive () directive adds after auth (). Let us see its syntax followed by a working code example.
given().auth().preemptive().basic("your username", "your password").get("your end point URL");
As you may see above, the preemptive authentication view sends the authentication details in the request header irrespective of being asked by the server. In the same line of implementation, we will see a simple API that uses preemptive authentication.
package org.example;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification;
public class BasicAuth {
@Test
public void getUserData() {
//Using the preemptive directive of basic auth to send credentials to the server
RequestSpecification httpRequest = RestAssured.given().auth().preemptive().basic("postman", "password");
Response res = httpRequest.get("https://postman-echo.com/basic-auth");
ResponseBody body = res.body();
//Converting the response body to string
String rbdy = body.asString();
System.out.println("Data from the GET API- "+rbdy);
}
}
The code example used above is a simple Get API where we are trying to fetch the details corresponding to the user. Note that the server needs the authentication details of the user to get a successful response. Let us glide through the code line-by-line.
RequestSpecification httpRequest = RestAssured.given().auth().preemptive().basic("postman", "password");
An object of RequestSpecification is created and using the preemptive directive the credentials of the user are sent in the header. Note that irrespective of being asked for the credentials these would be passed to the server.
Response res = httpRequest.get("https://postman-echo.com/basic-auth");
ResponseBody body = res.body();
The endpoint URL is accessed using the get method and the response is saved using the ResponseBody object.
String rbdy = body.asString();
System.out.println("Data from the GET API- "+rbdy);
Finally, we convert the response body to string and print the result. Similarly, you may add additional validations as per your requirements.
Console prints the response of the above code without errors.
And there you go! You have successfully retrieved the user data by simply adding the preemptive authentication in your code and passing the credentials.
Digest Authentication
It is somewhat similar to challenge-based authentication but is more secure as it uses a digestive key in subsequent requests. If at all it is intercepted by an eavesdropper, he will get access only to the transaction performed and not the user password. The transaction might be replayed but a new transaction cannot be made as the password is not exposed. Its syntax is similar to basic authentication-
given().auth().digest("your username", "your password").get("your end point URL")
Note that we cannot use the preemptive () similar to basic auth since this scheme uses only challenged authentication.
Form Authentication
There can be many cases when you need to pass the authentication credentials in an HTML form. This request is generally sent as a post method where the credentials entered in the form are used for authentication. So, if your application uses such a form-based authentication you can easily automate it using the form() scheme. The syntax for it follows-
given ().auth ().digest ("your username", "your password").get ("your endpoint URL")
given() .auth().form("your username", "your password").post("your end point URL")
If you use this approach then Rest Assured will first have to parse through the HTML response to find the fields for input and then send the form parameters. However, there is a high possibility that this approach might fail if the webpage is complex. Additionally, it would also fail if the context path is not included in the action attribute of the service. To optimize it to handle such cases, you may use the below format where you explicitly pass the required fields by providing the FormAuthConfig()-
given().auth().form("your username", "your password", new FormAuthConfig("/perform_signIn","user","password"))
OAuth Authentication
Another type of authentication is OAuth authentication. OAuth is an authorization framework that defines an identity protocol. It has wide usage in web applications and there are high chances that you will have to automate those authentication actions. These can be of two types viz, OAuth 1.0 and OAuth 2.0 which we will discuss now.
OAuth 1.0
Secured resources built using OAuth 1.0 requires passing consumer key, secret, access token, and token secret. The syntax it follows is -
given().auth().oauth(consumerKey, consumerSecret, accessToken, tokenSecret).get("your end point URL")
OAuth parameters read the required user input dynamically.
OAuth 2.0
There are cases when we need to generate an access token for a user session. This access token performs various transactions and helps maintain the user session. While using OAuth 2.0 you need to directly pass the access token generated when the user login using the below syntax-
given().auth().oauth2("Access token").get("your end point URL")
Using the access token you can easily request any of the resources secured using the OAuth scheme.
Now you may identify the types of authentication used in your web application. Similarly, you can use the corresponding authentication scheme to make full use of rest assured capabilities.
Note: Corresponding Postman tutorial for basic auth can be found at Basic Authentication in Postman.
Also, note that the video tutorial for this topic is available at Basic Authentication in Rest Assured
Key Takeaways
- Basic authentication helps you access the secured APIs and perform actions on the resources.
- Rest assured has four types of authentication schemes. They are basic, digest, form, and OAuth authentication.
- By default, rest assured uses a challenge-response mechanism. But, a preemptive directive sends the credentials without waiting for the server.
- OAuth has two types - OAuth1.0 or OAuth2.0.
PUT Request using Rest Assured
In earlier articles, we have seen how to read various components of an *HTTP Response (Headers,Body, and Status) and to Post a request* using Rest Assured. In this article, we will continue our exploration of HTTP request methods and move on to the next method - the PUT request method using REST Assured.
- What is an HTTP PUT Request Method?
- PUT Vs. POST Request.
- Status codes were obtained for PUT and POST requests.
- REST API example.
- How to implement a PUT Request using Rest Assured?
- Create JSON data using Simple JSON library.
- Send JSON content in the body of the Request.
- Validate the Response.
What is an HTTP PUT Request Method?
The *PUT method *(HTTP PUT request method) creates a new resource or updates (substitutes) a representation of the target resource with the request payload. This means a Put request updates a resource at a specified URI. It is also used to create a new resource at the given URI or replace the entire product entity.
The official HTTP RFC specifies:
- A PUT method puts or places a file or resource precisely at a specific URI.
- In case a file or a resource already exists at that URI, the PUT method replaces that file or resource.
- If there is no file or resource, PUT creates a new one.
- Responses to the PUT method are non-cacheable.
- PUT requests usually respond back with status code 200.
PUT Vs. POST Request
Let us now discuss the main differences between a PUT and a POST request.
PUT POST
This method is idempotent. This means it will produce the same results if executed more than once.
This method is not idempotent. It produces different results every time it is executed.
When we need to modify a single resource that is already part of resource collection, we call the PUT method.
POST method is called when a child resource is to be added under resources collection.
PUT method syntax : PUT /questions/{question-id}
POST method syntax: POST /questions
PUT works as specific.
POST works as abstract.
Put method makes use of the "UPDATE" query.
POST method makes use of the "CREATE" query.
In the PUT method, the client decides which URI resource should have.
In the POST method, the server decides which URI resource should have.
*PUT /vi/cake/orders/1234 indicates updation of a resource identified by "1234".
POST /vi/cake/orders* indicate that we are creating a new resource and return an identifier to describe the resource.
Status codes obtained for PUT and POST requests
The methods POST and PUT use the following status codes:
POST request
- 201 with a location header pointing to the new resource.
- 400 if the new item is not created.
PUT request
- 204 for OK/SUCCESS (but no content).
- 200 for OK with Content Body (Updated response).
- 400 if the data sent was invalid.
REST API example
Now we'll demonstrate the PUT request using Swagger UI. The URL for the PUT request is given here https://demoqa.com/BookStore/v1/Books/
Also, you can take a look at the Book Store Swagger
When we access this URL, we get the following screen.
In the above screen, we a trying to update a book record with ISBN="9781449325862". The two parameters that we will update are userId: "toolsqa_test" and ISBN: "9781449325865". When we execute this put request we get the following output.
We can see the status code 200 which we received from the server indicating the request was successful.
In the next section, we will implement the PUT request using Rest Assured.
How to implement a PUT Request using Rest Assured?
As explained in the tutorial on a *POST request, to create JSON objects, we will add a Simple JSON* library in the classpath in the code. Once this is done, we follow the below-given steps to put a request using REST Assured.
- Create JSON data using a simple JSON library.
- Send JSON content in the body of the request.
- Validate the response.
Let us discuss each of these steps below.
Create JSON data using Simple JSON library
We will use the same URL for implementing the PUT request using Rest Assured.
Endpoint: https://demoqa.com/BookStore/v1/Books/
The first step is to create a JSON data request that we need to send using the "put()" method. The following piece of code achieves this.
java import io.restassured.RestAssured; import io.restassured.specification.RequestSpecification; import org.junit.Test;
public class PUTMethod { @Test public void putMethod() {
String userId = "toolsqa_test";
String baseUrl = "https://demoqa.com";
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6InRlc3RpbmcxMjMiLCJwYXNzd29yZCI6IlBhc3N3b3JkQDEiLCJpYXQiOjE2Mjg1NjQyMjF9.lW8JJvJF7jKebbqPiHOBGtCAus8D9Nv1BK6IoIIMJQ4";
String isbn = "9781449325865";
RequestSpecification httpRequest = RestAssured.given().header("Authorization", "Bearer " + token).header("Content-Type", "application/json");
}
}
In the above code, we have used an ISBN parameter. Note that we already have a record with this ISBN. We will update this record by sending the PUT request. So for this ISBN, we will update the record with a new ISBN and a userId. We set a new value for the ISBN and also updated userId. So when we send this request, the record having ISBN="9781449325862" will have its userId="toolsqa_test" and ISBN updated to the new value, "9781449325865".
Send JSON content in the body of the Request
The generated request is sent to the server.
java Response res = httpRequest.body("{ "isbn": "" + isbn + "", "userId": "" + userId + ""}")
.put("/BookStore/v1/Book/9781449325862");
So in the above code, we have created a request body as a JSON string and then we call the "put()" method with this request by sending the ISBN as an argument. This ensures the record with the given ISBN is updated.
Validate the Response
The next step is to validate the response to ensure record updation. This is done by reading the status code of the response as follows:
java //validate the response System.out.println("The response code - " +res.getStatusCode());
Assert.assertEquals(res.getStatusCode(),200);
We have used the getStatusCode () method that returns the status code of the response. The value 200 indicates the PUT request was successful. The execution of the above tests gives the following response:
We see that the test case has passed.
The complete code for the PUT request demonstration using Rest Assured is as follows:
java package org.example;
import io.restassured.RestAssured; import io.restassured.specification.RequestSpecification; import org.junit.Assert; import org.junit.Test; import io.restassured.response.Response; import io.restassured.response.ResponseBody; import io.restassured.specification.RequestSpecification; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest;
public class PUTMethod { String userId= "toolsqa_test"; String baseUrl="https://demoqa.com"; String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6InRlc3RpbmcxMjMiLCJwYXNzd29yZCI6IlBhc3N3b3JkQDEiLCJpYXQiOjE2Mjg1NjQyMjF9.lW8JJvJF7jKebbqPiHOBGtCAus8D9Nv1BK6IoIIMJQ4"; String isbn ="9781449325865";
@Test
public void updateBook() {
RestAssured.baseURI = baseUrl;
RequestSpecification httpRequest = RestAssured.given().header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
//Calling the Delete API with request body
Response res = httpRequest.body("{ \"isbn\": \"" + isbn + "\", \"userId\": \"" + userId + "\"}").put("/BookStore/v1/Book/9781449325862");
//Fetching the response code from the request and validating the same
System.out.println("The response code - " +res.getStatusCode());
Assert.assertEquals(res.getStatusCode(),200);
}
}
The steps discussed above demonstrate the basic working of a PUT request and how it differs from a POST request. We used the JSON library to create JSON data sent in the content of the body of the request. We then validate the response using the status code after obtaining the response.
Note: Video tutorial for Put request is available at Put Request in Rest Assured
Key TakeAways
In this article, we discussed the PUT request.
- Put request is idempotent i.e. it produces the same results irrespective of the number of times the request is executed.
- The HTTP Put request updates or creates a resource.
- Put request usually responds with status code 200 when it is successful. The responses to the PUT request are not cacheable.
- We can use the Rest Assured library to put a request (using the put method). For this, we send a JSON object with the request and obtain a response.
- We validate the response by checking the status code.
In the next article of the Rest Assured series, we will discuss the "DELETE" method.
DELETE Request using Rest Assured
APIs form the base for any application and in every application, you perform a lot of different actions. These actions may range from adding, viewing, editing or deleting data. Since we have so many actions, we have to have a variety of tests to test the correctness of these actions. In our previous post, we learnt how to perform PUT requests using Rest Assured, and in this post, we will learn about the HTTP DELETE request method using the same method. If you have not gone through our previous articles I would urge you to go through our Rest Assured series step-by-step to understand the basics.
In this article, we will cover the below point-
- What is a Delete request?
- What are the different response codes for Delete Request?
- How to make a Delete Request using Rest Assured?
What is a delete request method?
An HTTP delete request is performed using the HTTP delete method (written as delete) which deletes a resource from the server identified by the URI we are sending through it. Also note that a delete method intends to change the state of the server. Although even a 200 code does not guarantee this. A few major points as highlighted in the official HTTP RFC with respect to the delete request method are listed below-
- The Delete method requests the server to delete the resource identified by the request URI.
- The resource deletion depends on the server and is deleted if prescribed for deletion. Additionally, the restoration implementation of the resource is also considerable.
- There is a deletion request for association between the resource and corresponding current functionality.
- It is similar to the rm UNIX command, where the current association of the resource is deleted instead of the previous ones(if any).
- Delete request method is placed under the idempotent category of the W3C documentation. It means that once a resource is requested for deletion, a request on the same resource again would give the same result as the resource has already been deleted.
- Delete method response is non-cacheable. The user cannot cache the server response for later use. Caching a delete request creates inconsistencies.
Let's see the response code of Delete request now.
What are the different response codes for Delete Request?
On sending the Delete request, the server responds with some response codes which signify the action performed by the Delete method. Consider the following code for the same-
- 202(Accepted): The server accepts the request but does not enact.
- 204(No Content)- A status code of 204 on the HTTP delete request method denotes successful enactment of the delete request without any content in the response.
- 200(OK)- The action was successful and the response message includes representation with the status.
- 404(Not Found) - When the server can't find the resource. The reason could either does not exist or previously deleted.
Now that we understand the basics of the Delete method we will see how we can send a Delete request using rest assured but before that let us see a simple DELETE request using Swagger UI. The URL for the request is-
The URL will show a screen like below-
Now you will have to select the first "Delete" in the BookStore section from this page.
Note: We have disabled the UserID to prevent an empty database. In practical application, you have to pass the required values to the server.
When you will click on the section, you will see a response depicting that the execution was a success. A specific response is received for cases like "bookid not found" or "user not found". See the snapshot below for more clarity-
In the introduction, we defined the delete method as something that can delete a resource. As a reminder, you can relate the "resource" with the pet record here. A generic term "resource" denotes varying resources in various requests. Now that we have seen how we may go about manually deleting a resource using swagger next we will try to automate the Delete request using Rest Assured.
How to make a Delete Request using Rest Assured?
We will now see how we can delete a resource from the server using rest assured. To do so, we will use BookStore APIs which we have been using in our previous examples. We will be automating the following use case-
- Get a list of available books for to a user using the Get User Data API.
- Delete a book corresponding to a specific ISBN and validate the response code using the Delete Book API.
- Verify that the execution of API from step 1 updates the book list and does not display the deleted book.
Let us now see the code for the above use case using Rest Assured.
package bookstore;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification;
public class DeleteBook {
String userId= "de5d75d1-59b4-487e-b632-f18bc0665c0d";
String baseUrl="https://demoqa.com/swagger/";
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6InRlc3RpbmcxMjMiLCJwYXNzd29yZCI6IlBhc3N3b3JkQDEiLCJpYXQiOjE2Mjg1NjQyMjF9.lW8JJvJF7jKebbqPiHOBGtCAus8D9Nv1BK6IoIIMJQ4";
String isbn ="9781449337711";
@BeforeTest
@AfterTest
public void getUserData() {
RestAssured.baseURI = baseUrl;
RequestSpecification httpRequest =
RestAssured.given().header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
Response res = httpRequest.get("/Account/v1/User/"+userId);
ResponseBody body = res.body();
//Converting the response body to string
String rbdy = body.asString();
System.out.println("Data from the GET API- "+rbdy);
}
@Test
public void deleteBook() {
RestAssured.baseURI = baseUrl;
RequestSpecification httpRequest = RestAssured.given().header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
//Calling the Delete API with request body
Response res = httpRequest.body("{ \"isbn\": \"" + isbn + "\", \"userId\": \"" + userId + "\"}").delete("/BookStore/v1/Book");
//Fetching the response code from the request and validating the same
System.out.println("The response code is - " +res.getStatusCode());
Assert.assertEquals(res.getStatusCode(),204);
}
}
Code walkthrough
Let's walk through the above code and see what we tried to achieve here. Note that the first method, i.e getUserData() is a simple GET method where we are trying to fetch the user data corresponding to a userId. You may refer to our article on Rest API using Rest Assured to understand the code if you have not gone through it yet. Additionally, you will notice the authorization field. For this particular tutorial on the delete request method, you need not worry about it. However, if you want to learn about authorization and OAuth 2.0, you can refer to our other tutorials. In this tutorial about the delete method, we will go through the code written under deleteBook() method here.
RestAssured.baseURI = baseUrl;
RequestSpecification httpRequest = RestAssured.given().header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
We first set up the request with the base URI. Next, we add the header(s) which also include the Authorization token using the RequestSpecification interface.
Response res = httpRequest.body("{ \"isbn\": \"" + isbn + "\", \"userId\": \"" + userId + "\"}").delete("/BookStore/v1/Book");
Next, store the Response object using the RequestSpecification object. Here we are passing the JSON body of our request along with the endpoint URL for the Delete method. The response gets store in a variable.
System.out.println("The response code is - " +res.getStatusCode());
Assert.assertEquals(res.getStatusCode(),204);
Lastly, we are simply printing the response status code and using the TestNG Assert to validate the same. Since in our case the expected response code is 204, our test would have it as the expected parameter.
Once you execute the code you will see that the getUserData method executes first, followed by the deleteBook method. And then finally the getUserData method runs again. This is because we used the @BeforeTest and @AfterTest for the getUserData method. The console would show you the result as below-
And there you go! You have successfully deleted a record from the user data using the Delete API. Now you may go ahead and try out the different API methods using Rest Assured and strengthen your concepts. In our next post, we will see how Basic Auth handles Rest Assured.
Note: Video tutorial for Delete request is available at Delete Request in Rest Assured
Key Takeaways
- The "delete" method deletes a resource from the server. It is quite similar to the rm UNIX command.
- The deletion of a resource is based on the server implementation and the response received is non-cacheable.
- The delete request returns any of the three types of response codes, i.e., 202, 204, and 200.
- The delete() method used with "path" or "pathParams" deletes the request.
- To verify the deletion, you may use assertion or any other relevant code as required.
What is JSON?
JSON is one of the most used Data-Exchange format. It is a light weight format represented as pure text. Due to the ability of almost all the languages to parse text, Json becomes independent of the programming language.
What is JSON?
JSON stands for JavaScript Object Notation. JSON is a human and machine-readable format to represent data as Structured Data. JSON is used primarily to transfer data from one computer to another or even between different programs on the same computer.
To visualize JSON, let us say that we want to represent a Person with following details in JSON
- First Name = Virender
- Last Name = Singh
- Age = 34
- Profession = Engineer
In JSON , this will turn out to be
{
"FirstName" : "Virender",
"LastName" : "Singh",
"Age" : 34,
"Profession": "Engineer"
}
At this point, we do not know the details of the JSON structure but we can still figure out what is being presented in JSON . Let us now understand different elements used in JSON .
Note: It is always beneficial to represent Keys without spaces in the name.
Key-Value Pairs
Key-Value pairs in JSON are used to represent a property of the Object. In the above example, we tried to represent a Person. This person has some properties like
- First Name
- Last Name
- Age
- Profession
Each of these properties have a value associated with it. For e.g First Name has a value of Virender. Similarly, Age has a value of 34. To write a Key-Value in JSON we have to follow these rules
- Key-value pairs are separated by a : (colon)
- Key is always present in Double Quotes " "
- Value could be anything depending on the Data Type
From the above example, a Key-Value pair is FirstName. Try to find other Key-Value pairs yourself
"FirstName" : "Virender"
Values can be of following data types
- Boolean: True or False
- Number: Numerical values
- Object: An associative array of Key-Value pairs
- Array: Associative array of just values
Object in JSON
In JSON an object is represented by a collection of Key-Value pairs. This collection of Key-Value pairs are grouped using { } (opening and closing curly braces}. Rules to writing an Object are
- Key-Value pairs should be separated by a , (Comma)
- Each Object should Start with an Opening { (Opening Curly Brace)
- Each Object should End with a Closing } (Closing Curly Brace)
An example here is the Person Object, discussed above. The Person object follows the rules mentioned for representing an Object
{
"FirstName" : "Virender",
"LastName" : "Singh",
"Age" : 34,
"Profession": "Engineer"
}
Array in JSON
Arrays are similar to Arrays that you know from any other programming language. In JSON an Array is collection of Values separated by Comma. Here are the rules to write an Array
- An Array starts with an opening [ (Bracket)
- An Array ends with a closing ] (Bracket)
- Values in the Array are separated by , (Comma)
To understand an Array let us add one more property to the Person Object. Let us add hobby also, a Person can have multiple hobbies. This makes it suitable to represent hobbies as an Array. As shown in the JSON below
{
"FirstName" : "Virender",
"LastName" : "Singh",
"Age" : 34,
"Profession": "Engineer",
"Hobbies" : ["Videos games", "Computers", "Music"]
}
See how multiple hobbies are represented using an Array. Array starts and ends with [ ] and contains values separated by ,
These are the different components of a JSON , using these components we can create complex JSON . As a small exercise for you, try to decode the JSON below and identify different structures involved in it.
{
"Description": "Map containing Country, Capital, Currency, and some States of that Country",
"Region": "Asia",
"Countries": [
{
"Country": "India",
"Data": {
"Capital": "New Delhi",
"minimum temp (Degree Celsius)": 6,
"maximum temp (Degree Celsius)": 45,
"Currency": "Rupee"
}
},
{
"Country": "Nepal",
"Data": {
"Capital": "Katmandu",
"minimum temp (Degree Celsius)": 9,
"maximum temp (Degree Celsius)": 23,
"Currency": "Nepalese rupee"
}
}
]
}
JSONPath and Query JSON using JSONPath
One of the most important advantage of JSON is that it is a light weight format that can be used to interchange data between computers and processes. JSON , like XML, is a format to provide structure to the data. If you are not familiar with JSON, please go through this tutorial first of Introduction to JSON.
In this tutorial, we will learn more about JSONPath and Query JSON using JSONPath. We will be covering the following topic:
- What is JSONPath?
- How to Query JSON with JSONPath?
- Different strategies to query JSON object and JSON Array.
What is JSONPath?
Every JSON object is composed on an inherent hierarchy and structure. Every JSON ends up creating a tree of nodes, where each node is a JSON Element. Let us take an example here, below is a simple JSON expressing a collection of countries
{
"Description": "Map containing Country, Capital, Currency, and some States of that Country",
"Region": "Asia",
"Countries": [
{
"Country": "India",
"Data": {
"Capital": "New Delhi",
"mintemp": 6,
"maxtemp": 45,
"Currency": "Rupee"
}
},
{
"Country": "Nepal",
"Data": {
"Capital": "Katmandu",
"mintemp": 9,
"maxtemp": 23,
"Currency": "Nepalese rupee"
}
}
]
}
At the top most level we have a Root node, which is basically the node containing all of the current JSON. Inside this root node, we have following nodes
- Description
- Region
- Countries
Description and Region are simple leaf nodes in the tree. But Countries is a non-leaf node, which further contains more nodes. Here Countries node contains an array of two countries. If we were to simply define a hierarchical relation between the Root node and any node in the JSON we can do like shown below.
Note: Let us represent Root node by $ and a relationship from parent to child with >>
Description node will be represented by $ >> Description.
Region node will be represented by $ >> Region.
Similarly, we can also define a relationship between the Root node and the zero'th item in the Countries array. Relationship will be $ >> Countries[0] where [ ] is the index operator to represent an item at n index in an Array of Countries.
In a way this hierarchy in JSON allows us to create a standard mechanism to traverse through specific parts of the JSON. A standard way to do this is called JSONPath.
How to Query JSON using JSONPath?
JSONPath creates a uniform standard and syntax to define different parts of a JSON document. JSONPath defines expressions to traverse through a JSON document to reach to a subset of the JSON. This topic is best understood by seeing it in action. We have created a web page which can help you evaluate a JSONPath. Use this page to practice writing JSONPath. This is the link to JSONPath evaluator.
Get Root node operator in JsonPath
Root node operator in JSON is represented by a $ sign. $ will return all the nodes inside the JSON document. To try this out open the JSONPath evaluator page and type $ in the JSONPath field. As shown in the image below
Get Children operator in JSONPath
In order to get children of a given node, we can use the Dot (.) operator or the ['childname'] operator. In order to get all the Countries we can have JSONPath as
- $.Countries
- $['Countries']
Output will be
[
[
{
"Country": "India",
"Data": {
"Capital": "New Delhi",
"mintemp": 6,
"maxtemp": 45,
"Currency": "Rupee"
}
},
{
"Country": "Nepal",
"Data": {
"Capital": "Katmandu",
"mintemp": 9,
"maxtemp": 23,
"Currency": "Nepalese rupee"
}
}
]
]
Wildcard operator in JSONPath
Wild card operator in JSONPath is (Star or Asterisk) symbol. This is literally means everything under that node. For example, if you want to display the Data nodes of all the countries you can simple write following JSONPat
- $.Countries[].Data*
- $['Countries'][].Data*
This will display the Data nodes of all the countries
[
{
"Capital": "New Delhi",
"mintemp": 6,
"maxtemp": 45,
"Currency": "Rupee"
},
{
"Capital": "Katmandu",
"mintemp": 9,
"maxtemp": 23,
"Currency": "Nepalese rupee"
}
]
Array Index operator in JSONPath
Sometimes it is required to access a particular entry at a given index in the JSON array. We can use the Array Index [i,j,k... ] to identify an entry at a particular index. In the above example, let us find out the last Country entry in the Countries array.
- $.Countries[-1]
- $['Countries'][-1]
Here -1 stands for the last item in the Array. You can also refer to the last item by giving a positive value to the index. For e.g.
- $.Countries[1]
- $['Countries'][1]
Output will look like this
[
{
"Country": "Nepal",
"Data": {
"Capital": "Katmandu",
"mintemp": 9,
"maxtemp": 23,
"Currency": "Nepalese rupee"
}
}
]
Note: Array index starts from 0. Hence to refer to the second item in the array we have to use 1 as the index.
Array index is just not limited to displaying only 1 item. We can extract multiple items from the array at different indexes. The syntax to do so is [i,j,k..]. For e.g. to extract the first 2 array items we will write the JSONPath as
- $.Countries[0,1]
- $['Countries'][0,1]
Output will be
[
{
"Country": "India",
"Data": {
"Capital": "New Delhi",
"mintemp": 6,
"maxtemp": 45,
"Currency": "Rupee"
}
},
{
"Country": "Nepal",
"Data": {
"Capital": "Katmandu",
"mintemp": 9,
"maxtemp": 23,
"Currency": "Nepalese rupee"
}
}
]
For the JsonPath shown below, we will change our Json to have more nodes. Let us take a look at a Json representing collections of books. Here is the Json, from now on use this Json in the JsonPath Evaluator
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
Array slice operator in JsonPath
Array slice operator is wonderful operator to extract selected items from Json. Taking the example of books, what if we want to retrieve every alternative book in the Json. To do that we will need the Array, Slice operator. Syntax of Array Slice operator is [StartIndex : EndIndex : Steps]. Let find out what are books at odd number in the books collection. JsonPath will look like
- $..book[1,4,2]
- $..['book'][1,4,2]
the output of the above JsonPath will be
[
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
}
]
Expressions in JSONPath
Usage of Expressions in JsonPath is a very good feature to have concise and complex JsonPaths. Expressions in JsonPath are basically code snippets that evaluate to the Boolean value. Based on the outcome only the nodes which meet the criteria are selected. Let us see more about it, but before that make sure you have gone through following tutorials on basics of Json and JsonPath
In this tutorial, we will use a sample Json which has a few items in the Array. Please copy the below Json in our JsonPath Evaluator
{
"books": [
{
"isbn": "9781593275846",
"title": "Eloquent JavaScript, Second Edition",
"subtitle": "A Modern Introduction to Programming",
"author": "Marijn Haverbeke",
"published": "2014-12-14T00:00:00.000Z",
"publisher": "No Starch Press",
"pages": 472,
"description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
"website": "https://eloquentjavascript.net/"
},
{
"isbn": "9781449331818",
"title": "Learning JavaScript Design Patterns",
"subtitle": "A JavaScript and jQuery Developer's Guide",
"author": "Addy Osmani",
"published": "2012-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "With Learning JavaScript Design Patterns, you'll learn how to write beautiful, structured, and maintainable JavaScript by applying classical and modern design patterns to the language. If you want to keep your code efficient, more manageable, and up-to-date with the latest best practices, this book is for you.",
"website": "https://www.addyosmani.com/resources/essentialjsdesignpatterns/book/"
},
{
"isbn": "9781449365035",
"title": "Speaking JavaScript",
"subtitle": "An In-Depth Guide for Programmers",
"author": "Axel Rauschmayer",
"published": "2014-02-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 460,
"description": "Like it or not, JavaScript is everywhere these days-from browser to server to mobile-and now you, too, need to learn the language or dive deeper than you have. This concise book guides you into and through JavaScript, written by a veteran programmer who once found himself in the same position.",
"website": "https://speakingjs.com/"
},
{
"isbn": "9781491950296",
"title": "Programming JavaScript Applications",
"subtitle": "Robust Web Architecture with Node, HTML5, and Modern JS Libraries",
"author": "Eric Elliott",
"published": "2014-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "Take advantage of JavaScript's power to build robust web-scale or enterprise applications that are easy to extend and maintain. By applying the design patterns outlined in this practical book, experienced JavaScript developers will learn how to write flexible and resilient code that's easier-yes, easier-to work with as your code base grows.",
"website": "https://chimera.labs.oreilly.com/books/1234000000262/index.html"
},
{
"isbn": "9781593277574",
"title": "Understanding ECMAScript 6",
"subtitle": "The Definitive Guide for JavaScript Developers",
"author": "Nicholas C. Zakas",
"published": "2016-09-03T00:00:00.000Z",
"publisher": "No Starch Press",
"pages": 352,
"description": "ECMAScript 6 represents the biggest update to the core of JavaScript in the history of the language. In Understanding ECMAScript 6, expert developer Nicholas C. Zakas provides a complete guide to the object types, syntax, and other exciting changes that ECMAScript 6 brings to JavaScript.",
"website": "https://leanpub.com/understandinges6/read"
},
{
"isbn": "9781491904244",
"title": "You Don't Know JS",
"subtitle": "ES6 & Beyond",
"author": "Kyle Simpson",
"published": "2015-12-27T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 278,
"description": "No matter how much experience you have with JavaScript, odds are you don’t fully understand the language. As part of the 'You Don’t Know JS' series, this compact guide focuses on new features available in ECMAScript 6 (ES6), the latest version of the standard upon which JavaScript is built.",
"website": "https://github.com/getify/You-Dont-Know-JS/tree/master/es6%20&%20beyond"
},
{
"isbn": "9781449325862",
"title": "Git Pocket Guide",
"subtitle": "A Working Introduction",
"author": "Richard E. Silverman",
"published": "2013-08-02T00:00:00.000Z",
"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"
},
{
"isbn": "9781449337711",
"title": "Designing Evolvable Web APIs with ASP.NET",
"subtitle": "Harnessing the Power of the Web",
"author": "Glenn Block, et al.",
"published": "2014-04-07T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 538,
"description": "Design and build Web APIs for a broad range of clients—including browsers and mobile devices—that can adapt to change over time. This practical, hands-on guide takes you through the theory and tools you need to build evolvable HTTP services with Microsoft’s ASP.NET Web API framework. In the process, you’ll learn how design and implement a real-world Web API.",
"website": "https://chimera.labs.oreilly.com/books/1234000001708/index.html"
}
]
}
Expressions in JsonPath
Expressions in JsonPath are one of the most powerful features of JsonPath. Note that expressions are also available in Xpath and CSS Selectors. Expressions help you create a condition that is evaluated to true or false. There are two important symbols that you have to understand before you can create Expressions in JsonPath.
- ? : Question mark, marks the beginning of an expression. Syntax used [? (Expression)]
- @ : At symbol, signifies the current node being processed. Syntax used $.books[?(@.price > 100)]
Let us now take a simple task from the Json above.
- Find out all the books which have the pages greater than 460
To create a JsonPath which can get us all the books which have pages greater than 460, we have to break the problem into two parts
- Create a JsonPath to retrieve all books
- Append an expression to filter all the books which have pages greater than 460
To get all the books we can create a simple JsonPath: $.books . Now we have to add an expression in the array books. To do so we will simple start an expression with***?*** sign and then add a filter on the current node @ . A simple expression will look like this ?(@.pages > 460).
If we combine JsonPath with the expression we will get this: $.books[?(@.pages > 460)]
In the JsonPath Evaluator, simply enter this expression and see the results. As shown in the image below
The result will be all the books with page numbers greater than 460. Here is the result Json
[
{
"isbn": "9781593275846",
"title": "Eloquent JavaScript, Second Edition",
"subtitle": "A Modern Introduction to Programming",
"author": "Marijn Haverbeke",
"published": "2014-12-14T00:00:00.000Z",
"publisher": "No Starch Press",
"pages": 472,
"description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
"website": "https://eloquentjavascript.net/"
},
{
"isbn": "9781449337711",
"title": "Designing Evolvable Web APIs with ASP.NET",
"subtitle": "Harnessing the Power of the Web",
"author": "Glenn Block, et al.",
"published": "2014-04-07T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 538,
"description": "Design and build Web APIs for a broad range of clients—including browsers and mobile devices—that can adapt to change over time. This practical, hands-on guide takes you through the theory and tools you need to build evolvable HTTP services with Microsoft’s ASP.NET Web API framework. In the process, you’ll learn how design and implement a real-world Web API.",
"website": "https://chimera.labs.oreilly.com/books/1234000001708/index.html"
}
]
Logical operators in JsonPath
Just like any programming language, JsonPath supports all the logical operators. Below is the list of Logical Operators that we can use to create expressions. Every logical operator is discussed in detail below.
Operator Description
==
left is equal to right (note that 1 is not equal to '1').
!=
left is not equal to right.
<
left is less than right.
<=
left is less or equal to right.
>
left is greater than right.
>=
left is greater than or equal to right.
Try all the above examples and also try to create more expressions based on your needs. This way you will learn more about the JsonPath expressions.
Equals to (==) operator in JsonPath
As the name suggests the operator checks if the left-hand side is equal to the right-hand side. Let us find out all the books that have 352 pages. Here is the JsonPath
JsonPath: $.books[?(@.pages == 352)]
Result will be:
[
{
"isbn": "9781593277574",
"title": "Understanding ECMAScript 6",
"subtitle": "The Definitive Guide for JavaScript Developers",
"author": "Nicholas C. Zakas",
"published": "2016-09-03T00:00:00.000Z",
"publisher": "No Starch Press",
"pages": 352,
"description": "ECMAScript 6 represents the biggest update to the core of JavaScript in the history of the language. In Understanding ECMAScript 6, expert developer Nicholas C. Zakas provides a complete guide to the object types, syntax, and other exciting changes that ECMAScript 6 brings to JavaScript.",
"website": "https://leanpub.com/understandinges6/read"
}
]
Not equal to (!=) operator in JsonPath
When we want to exclude a particular set of values based on a condition we use the not equal to operator. Let us just invert the example above and find all the books that have page numbers not equal to 352.
JsonPath: $.books[?(@.pages != 352)]
Result will be:
[
{
"isbn": "9781593275846",
"title": "Eloquent JavaScript, Second Edition",
"subtitle": "A Modern Introduction to Programming",
"author": "Marijn Haverbeke",
"published": "2014-12-14T00:00:00.000Z",
"publisher": "No Starch Press",
"pages": 472,
"description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
"website": "https://eloquentjavascript.net/"
},
{
"isbn": "9781449331818",
"title": "Learning JavaScript Design Patterns",
"subtitle": "A JavaScript and jQuery Developer's Guide",
"author": "Addy Osmani",
"published": "2012-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "With Learning JavaScript Design Patterns, you'll learn how to write beautiful, structured, and maintainable JavaScript by applying classical and modern design patterns to the language. If you want to keep your code efficient, more manageable, and up-to-date with the latest best practices, this book is for you.",
"website": "https://www.addyosmani.com/resources/essentialjsdesignpatterns/book/"
},
{
"isbn": "9781449365035",
"title": "Speaking JavaScript",
"subtitle": "An In-Depth Guide for Programmers",
"author": "Axel Rauschmayer",
"published": "2014-02-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 460,
"description": "Like it or not, JavaScript is everywhere these days-from browser to server to mobile-and now you, too, need to learn the language or dive deeper than you have. This concise book guides you into and through JavaScript, written by a veteran programmer who once found himself in the same position.",
"website": "https://speakingjs.com/"
},
{
"isbn": "9781491950296",
"title": "Programming JavaScript Applications",
"subtitle": "Robust Web Architecture with Node, HTML5, and Modern JS Libraries",
"author": "Eric Elliott",
"published": "2014-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "Take advantage of JavaScript's power to build robust web-scale or enterprise applications that are easy to extend and maintain. By applying the design patterns outlined in this practical book, experienced JavaScript developers will learn how to write flexible and resilient code that's easier-yes, easier-to work with as your code base grows.",
"website": "https://chimera.labs.oreilly.com/books/1234000000262/index.html"
},
{
"isbn": "9781491904244",
"title": "You Don't Know JS",
"subtitle": "ES6 & Beyond",
"author": "Kyle Simpson",
"published": "2015-12-27T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 278,
"description": "No matter how much experience you have with JavaScript, odds are you don’t fully understand the language. As part of the 'You Don’t Know JS' series, this compact guide focuses on new features available in ECMAScript 6 (ES6), the latest version of the standard upon which JavaScript is built.",
"website": "https://github.com/getify/You-Dont-Know-JS/tree/master/es6+&+beyond"
},
{
"isbn": "9781449325862",
"title": "Git Pocket Guide",
"subtitle": "A Working Introduction",
"author": "Richard E. Silverman",
"published": "2013-08-02T00:00:00.000Z",
"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"
},
{
"isbn": "9781449337711",
"title": "Designing Evolvable Web APIs with ASP.NET",
"subtitle": "Harnessing the Power of the Web",
"author": "Glenn Block, et al.",
"published": "2014-04-07T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 538,
"description": "Design and build Web APIs for a broad range of clients—including browsers and mobile devices—that can adapt to change over time. This practical, hands-on guide takes you through the theory and tools you need to build evolvable HTTP services with Microsoft’s ASP.NET Web API framework. In the process, you’ll learn how design and implement a real-world Web API.",
"website": "https://chimera.labs.oreilly.com/books/1234000001708/index.html"
}
]
Less than ( < ) operator in JsonPath
Less than operator, as the name suggests will return all the values that are less than the value given on the right. Let us find out all the books with pages less than 352.
JsonPath: $.books[?(@.pages < 352)]
Result will be:
[
{
"isbn": "9781449331818",
"title": "Learning JavaScript Design Patterns",
"subtitle": "A JavaScript and jQuery Developer's Guide",
"author": "Addy Osmani",
"published": "2012-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "With Learning JavaScript Design Patterns, you'll learn how to write beautiful, structured, and maintainable JavaScript by applying classical and modern design patterns to the language. If you want to keep your code efficient, more manageable, and up-to-date with the latest best practices, this book is for you.",
"website": "https://www.addyosmani.com/resources/essentialjsdesignpatterns/book/"
},
{
"isbn": "9781491950296",
"title": "Programming JavaScript Applications",
"subtitle": "Robust Web Architecture with Node, HTML5, and Modern JS Libraries",
"author": "Eric Elliott",
"published": "2014-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "Take advantage of JavaScript's power to build robust web-scale or enterprise applications that are easy to extend and maintain. By applying the design patterns outlined in this practical book, experienced JavaScript developers will learn how to write flexible and resilient code that's easier-yes, easier-to work with as your code base grows.",
"website": "https://chimera.labs.oreilly.com/books/1234000000262/index.html"
},
{
"isbn": "9781491904244",
"title": "You Don't Know JS",
"subtitle": "ES6 & Beyond",
"author": "Kyle Simpson",
"published": "2015-12-27T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 278,
"description": "No matter how much experience you have with JavaScript, odds are you don’t fully understand the language. As part of the 'You Don’t Know JS' series, this compact guide focuses on new features available in ECMAScript 6 (ES6), the latest version of the standard upon which JavaScript is built.",
"website": "https://github.com/getify/You-Dont-Know-JS/tree/master/es6+&+beyond"
},
{
"isbn": "9781449325862",
"title": "Git Pocket Guide",
"subtitle": "A Working Introduction",
"author": "Richard E. Silverman",
"published": "2013-08-02T00:00:00.000Z",
"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"
}
]
Less than equal to ( <= ) operator in JsonPath
This operator will let you get all the values that are less than or equal to a given value. Let us find out all the books which have pages less than or equal to 352.
JsonPath: $.books[?(@.pages < 352)]
Result will be:
[
{
"isbn": "9781449331818",
"title": "Learning JavaScript Design Patterns",
"subtitle": "A JavaScript and jQuery Developer's Guide",
"author": "Addy Osmani",
"published": "2012-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "With Learning JavaScript Design Patterns, you'll learn how to write beautiful, structured, and maintainable JavaScript by applying classical and modern design patterns to the language. If you want to keep your code efficient, more manageable, and up-to-date with the latest best practices, this book is for you.",
"website": "https://www.addyosmani.com/resources/essentialjsdesignpatterns/book/"
},
{
"isbn": "9781491950296",
"title": "Programming JavaScript Applications",
"subtitle": "Robust Web Architecture with Node, HTML5, and Modern JS Libraries",
"author": "Eric Elliott",
"published": "2014-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "Take advantage of JavaScript's power to build robust web-scale or enterprise applications that are easy to extend and maintain. By applying the design patterns outlined in this practical book, experienced JavaScript developers will learn how to write flexible and resilient code that's easier-yes, easier-to work with as your code base grows.",
"website": "https://chimera.labs.oreilly.com/books/1234000000262/index.html"
},
{
"isbn": "9781491904244",
"title": "You Don't Know JS",
"subtitle": "ES6 & Beyond",
"author": "Kyle Simpson",
"published": "2015-12-27T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 278,
"description": "No matter how much experience you have with JavaScript, odds are you don’t fully understand the language. As part of the 'You Don’t Know JS' series, this compact guide focuses on new features available in ECMAScript 6 (ES6), the latest version of the standard upon which JavaScript is built.",
"website": "https://github.com/getify/You-Dont-Know-JS/tree/master/es6+&+beyond"
},
{
"isbn": "9781449325862",
"title": "Git Pocket Guide",
"subtitle": "A Working Introduction",
"author": "Richard E. Silverman",
"published": "2013-08-02T00:00:00.000Z",
"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"
}
]
Greater than ( > ) operator in JsonPath
Greater than operator will let you get all the values which are greater than the value on the left-hand side. Let us find all the books which have pages more than 460.
JsonPath: $.books[?(@.pages > 460)]
Result will be:
[
{
"isbn": "9781593275846",
"title": "Eloquent JavaScript, Second Edition",
"subtitle": "A Modern Introduction to Programming",
"author": "Marijn Haverbeke",
"published": "2014-12-14T00:00:00.000Z",
"publisher": "No Starch Press",
"pages": 472,
"description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
"website": "https://eloquentjavascript.net/"
},
{
"isbn": "9781449337711",
"title": "Designing Evolvable Web APIs with ASP.NET",
"subtitle": "Harnessing the Power of the Web",
"author": "Glenn Block, et al.",
"published": "2014-04-07T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 538,
"description": "Design and build Web APIs for a broad range of clients—including browsers and mobile devices—that can adapt to change over time. This practical, hands-on guide takes you through the theory and tools you need to build evolvable HTTP services with Microsoft’s ASP.NET Web API framework. In the process, you’ll learn how design and implement a real-world Web API.",
"website": "https://chimera.labs.oreilly.com/books/1234000001708/index.html"
}
]
Greater than equal to ( >= ) operator in JsonPath
Greater than equal will let you get all the values that are equal to or greater than value on the right-hand side. Let's get all the books with pages either greater than or equal to 460
JsonPath: $.books[?(@.pages >= 460)]
Result will be:
[
{
"isbn" : "9781593275846",
"title" : "Eloquent JavaScript, Second Edition",
"subtitle" : "A Modern Introduction to Programming",
"author" : "Marijn Haverbeke",
"published" : "2014-12-14T00:00:00.000Z",
"publisher" : "No Starch Press",
"pages" : 472,
"description" : "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
"website" : "https://eloquentjavascript.net/"
},
{
"isbn" : "9781449365035",
"title" : "Speaking JavaScript",
"subtitle" : "An In-Depth Guide for Programmers",
"author" : "Axel Rauschmayer",
"published" : "2014-02-01T00:00:00.000Z",
"publisher" : "O'Reilly Media",
"pages" : 460,
"description" : "Like it or not, JavaScript is everywhere these days-from browser to server to mobile-and now you, too, need to learn the language or dive deeper than you have. This concise book guides you into and through JavaScript, written by a veteran programmer who once found himself in the same position.",
"website" : "https://speakingjs.com/"
},
{
"isbn" : "9781449337711",
"title" : "Designing Evolvable Web APIs with ASP.NET",
"subtitle" : "Harnessing the Power of the Web",
"author" : "Glenn Block, et al.",
"published" : "2014-04-07T00:00:00.000Z",
"publisher" : "O'Reilly Media",
"pages" : 538,
"description" : "Design and build Web APIs for a broad range of clients\u2014including browsers and mobile devices\u2014that can adapt to change over time. This practical, hands-on guide takes you through the theory and tools you need to build evolvable HTTP services with Microsoft\u2019s ASP.NET Web API framework. In the process, you\u2019ll learn how design and implement a real-world Web API.",
"website" : "https://chimera.labs.oreilly.com/books/1234000001708/index.html"
}
]
In the next chapters, we will use JsonPath in Rest-Assured and see how we can write efficient validations.
Deserialize JSON Array to List
In this tutorial we will talk about the advance usage of JSONPath in Rest Assured which is How to DeSerialize JSON Array to List. If you have reached here directly, I would suggest that you go through these links before starting with this.
DeSerialize JSON Array to List
Earlier we saw how JSONPath can be used to make a deterministic and accurate search in the Response JSON. With this capability, we are able to write better test validations.
In this chapter, we will focus on how we can perform an advance search in the Response JSON by Deserialize JSON Array to List. We will also learn how we can represent the searched section of Response in various Java data structures. This tutorial is structured based on different methods available in JSONPath class.
DeSerialize JSON Array to List of String using JSONPath
JsonPath class has two overloaded methods (jsonPath.getList) to extract JSON nodes as a Java List. Here are the methods
- JsonPath.getList(String) method lets us get the searched nodes as a List of String
- JsonPath.getList(String, Class T) method let us get the searched nodes as a List of T type
To understand we will make a Get call to the https://bookstore.demoqa.com/swagger/#/BookStore/BookStoreV1BooksGet REST service. This service returns a JSON response consisting of a collection of books(Array of Books). Each book in the collection is a JSON object containing values describing the book. You can directly open the URL in the browser to see the output.
To understand JsonPath.getList(String) method we will try to retrieve all the books as a List of String (List<String>
). All we need to do is give "books" as the JSONPath.
Below is the code
@Test
public void JsonPathUsage() throws MalformedURLException
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/books/getallbooks";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("");
// First get the JsonPath object instance from the Response interface
JsonPath jsonPathEvaluator = response.jsonPath();
// Read all the books as a List of String. Each item in the list
// represent a book node in the REST service Response
List<String> allBooks = jsonPathEvaluator.getList("books.title");
// Iterate over the list and print individual book item
for(String book : allBooks)
{
System.out.println("Book: " + book);
}
}
Once you run this, the output will be look like this:
Book: Eloquent JavaScript, Second Edition
Book: Learning JavaScript Design Patterns
Book: Speaking JavaScript
Book: Programming JavaScript Applications
Book: Understanding ECMAScript 6
Book: You Don't Know JS
Book: Git Pocket Guide
Book: Designing Evolvable Web APIs with ASP.NET
DeSerialize JSON Array to List of Class (Type T) Object using JSONPath
We all are familiar with Serialization and De-serialization support built-in in Rest-Assured. Let us try to convert the searched nodes directly into an object representation. In this example let us retrieve all the books from the JSON response. We will retrieve all the books as a List of Books class. In order to do this, we will first create a Class Representation of a Book. Get all the properties of JSON Book entity and create a class with those member variables. A book can be simply represented by a POJO class as shown in the code below.
public class Book {
String isbn;
String title;
String subtitle;
String author;
String published;
String publisher;
int pages;
String description;
String website;
}
Now to get the response converted into a List of Books we will simply use the JsonPath.getList(String, Class T) method.
- First parameter is the JSONPath ("books") in this case
- Second parameter is the Type name to which we want the response to be converted. Book class in this case.
Below is the complete code showing the usage
@Test
public void JsonPathUsage() throws MalformedURLException
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/books/getallbooks";
RequestSpecification httpRequest = RestAssured.given();
Response response = httpRequest.get("");
// First get the JsonPath object instance from the Response interface
JsonPath jsonPathEvaluator = response.jsonPath();
// Read all the books as a List of String. Each item in the list
// represent a book node in the REST service Response
List<Book> allBooks = jsonPathEvaluator.getList("books", Book.class);
// Iterate over the list and print individual book item
// Note that every book entry in the list will be complete Json object of book
for(Book book : allBooks)
{
System.out.println("Book: " + book.title);
}
}
This is a very good feature of Rest-Assured that enables us to get a targeted part of the response.
Deserialize JSON Array to an Array
Let us learn How to Deserialize JSON Array to an Array? But before we start, it is very important that you go through the previous set of Tutorials. You can take a look at the tutorials here:
Deserialize JSON Array to an Array
This tutorial further builds upon our understanding of Deserialization of a JSON Response into an object of a given Type. Please go through the basics of Serialization and Deserialization in the below tutorial
We will take an example of demo REST API endpoint: https://bookstore.demoqa.com/swagger/#/BookStore/BookStoreV1BooksGet If you hit this Endpoint you will get a Collection of books in the Json response. Below is the response:
{
"books": [
{
"isbn": "9781593275846",
"title": "Eloquent JavaScript, Second Edition",
"subtitle": "A Modern Introduction to Programming",
"author": "Marijn Haverbeke",
"published": "2014-12-14T00:00:00.000Z",
"publisher": "No Starch Press",
"pages": 472,
"description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
"website": "http:\/\/eloquentjavascript.net\/"
},
{
"isbn": "9781449331818",
"title": "Learning JavaScript Design Patterns",
"subtitle": "A JavaScript and jQuery Developer's Guide",
"author": "Addy Osmani",
"published": "2012-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "With Learning JavaScript Design Patterns, you'll learn how to write beautiful, structured, and maintainable JavaScript by applying classical and modern design patterns to the language. If you want to keep your code efficient, more manageable, and up-to-date with the latest best practices, this book is for you.",
"website": "http:\/\/www.addyosmani.com\/resources\/essentialjsdesignpatterns\/book\/"
},
{
"isbn": "9781449365035",
"title": "Speaking JavaScript",
"subtitle": "An In-Depth Guide for Programmers",
"author": "Axel Rauschmayer",
"published": "2014-02-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 460,
"description": "Like it or not, JavaScript is everywhere these days-from browser to server to mobile-and now you, too, need to learn the language or dive deeper than you have. This concise book guides you into and through JavaScript, written by a veteran programmer who once found himself in the same position.",
"website": "http:\/\/speakingjs.com\/"
},
{
"isbn": "9781491950296",
"title": "Programming JavaScript Applications",
"subtitle": "Robust Web Architecture with Node, HTML5, and Modern JS Libraries",
"author": "Eric Elliott",
"published": "2014-07-01T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 254,
"description": "Take advantage of JavaScript's power to build robust web-scale or enterprise applications that are easy to extend and maintain. By applying the design patterns outlined in this practical book, experienced JavaScript developers will learn how to write flexible and resilient code that's easier-yes, easier-to work with as your code base grows.",
"website": "http:\/\/chimera.labs.oreilly.com\/books\/1234000000262\/index.html"
},
{
"isbn": "9781593277574",
"title": "Understanding ECMAScript 6",
"subtitle": "The Definitive Guide for JavaScript Developers",
"author": "Nicholas C. Zakas",
"published": "2016-09-03T00:00:00.000Z",
"publisher": "No Starch Press",
"pages": 352,
"description": "ECMAScript 6 represents the biggest update to the core of JavaScript in the history of the language. In Understanding ECMAScript 6, expert developer Nicholas C. Zakas provides a complete guide to the object types, syntax, and other exciting changes that ECMAScript 6 brings to JavaScript.",
"website": "https:\/\/leanpub.com\/understandinges6\/read"
},
{
"isbn": "9781491904244",
"title": "You Don't Know JS",
"subtitle": "ES6 & Beyond",
"author": "Kyle Simpson",
"published": "2015-12-27T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 278,
"description": "No matter how much experience you have with JavaScript, odds are you don\u2019t fully understand the language. As part of the 'You Don\u2019t Know JS' series, this compact guide focuses on new features available in ECMAScript 6 (ES6), the latest version of the standard upon which JavaScript is built.",
"website": "https:\/\/github.com\/getify\/You-Dont-Know-JS\/tree\/master\/es6%20&%20beyond"
},
{
"isbn": "9781449325862",
"title": "Git Pocket Guide",
"subtitle": "A Working Introduction",
"author": "Richard E. Silverman",
"published": "2013-08-02T00:00:00.000Z",
"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": "http:\/\/chimera.labs.oreilly.com\/books\/1230000000561\/index.html"
},
{
"isbn": "9781449337711",
"title": "Designing Evolvable Web APIs with ASP.NET",
"subtitle": "Harnessing the Power of the Web",
"author": "Glenn Block, et al.",
"published": "2014-04-07T00:00:00.000Z",
"publisher": "O'Reilly Media",
"pages": 538,
"description": "Design and build Web APIs for a broad range of clients\u2014including browsers and mobile devices\u2014that can adapt to change over time. This practical, hands-on guide takes you through the theory and tools you need to build evolvable HTTP services with Microsoft\u2019s ASP.NET Web API framework. In the process, you\u2019ll learn how design and implement a real-world Web API.",
"website": "http:\/\/chimera.labs.oreilly.com\/books\/1234000001708\/index.html"
}
]
}
This JSON Response contains an Array of Books. Each item in the JSON Array represents a book and has properties of a book like "isbn", "title", "author" etc. In the earlier tutorial, we learnt How to Deserialize JSON Resposne of Single Node to an Instance of Class. However, in Deserializing a Collection of Nodes into Array becomes little tricky. Let us see how Rest Assured helps us achieve this quickly and without writing any boilerplate code.
Deserialize JSON Array to an Array using JSONPath of Rest Assured?
Similarly, we can convert a Json Array into a Java Array. JsonPath class has method called getObject. This method can be used to convert the response directly into a Java Array of Book. The only thing the we need to do is pass Book[].class as the second argument to the method to signify that we want the Json to be deserialized into an Array of Book. Here is the code that will do this
@Test
public void JsonArrayToArray()
{
RestAssured.baseURI = "https://restapi.demoqa.com/utilities/books/getallbooks";
RequestSpecification request = RestAssured.given();
Response response = request.get();
System.out.println("Response Body -> " + response.body().asString());
// We can convert the Json Response directly into a Java Array by using
// JsonPath.getObject method. Here we have to specify that we want to
// deserialize the Json into an Array of Book. This can be done by specifying
// Book[].class as the second argument to the getObject method.
Book[] books = response.jsonPath().getObject("books",Book[].class );
for(Book book : books)
{
System.out.println("Book title " + book.title);
}
}
The above two techniques are important to write Concise test code. Apart from writing concise tests by using the above techniques we fully utilize the features given to us by Rest-Assured. This helps us have lesser dependencies outside Rest Assured and also enables us to write reliable tests.
What is API Documentation, and Why It Matters?
Automation has a crucial position in this technology-centric world. Faster and short releases are in vogue. In addition to that, the agile development method has gained a foothold in the software industry. There has been a remarkable change in the way we develop software automation tests in agile.
Rising Need For API Testing
The old practice of having only the GUI tests for automation does not exist anymore. Moreover, the problems in software testing with the GUI tests are several. To enumerate a few:
- The tests may fail frequently owing to frequent changes in the UI
- Additional maintenance and refactoring efforts associated with flaky tests
- Moreover, its a time-consuming test process and has slower feedback.
- It requires a large number of initiatives in terms of maintainability and stability.
In a two week aligned sprint, it gets altogether challenging to have a UI ready and test it simultaneously for test coverage results. Unlike the GUI tests, the API testing doesn't rely on the UI. It becomes much more comfortable to conjoin/ integrate them under Agile Development. Additionally, it can be brought in much earlier at the development stage. The increasing/ rising trend of API Testing underlines this further.
An API abstracts the implementation layer, exposing only the objects needed by the developer. It consists of REST methods. Moreover, these REST methods fetch, manipulate, and delete the data in the application's database. We need to familiarize ourselves with the workings of REST API basics. Please go through the REST API tutorials to be acquainted with those.
Once we have familiarised ourselves with the REST API basics, it is time to learn to test the REST APIs. To test a REST API, we must understand the interface of the API. This includes:
- The operations we can perform using the API
- Structure of the requests
- Structure of responses
Therefore, an essential step in that direction would be to look at the API Documentations. In this article, we are going to cover:-
- Why do we need an API Documentation?
- What is an API Documentation?
- How to use API Document?
- What are the various types of tests for API?
Let us first understand the benefits of API Documentation.
Why do we need an API Documentation?
Documentation of any technology has a direct impact on the adoption and its usage. Without the knowledge of the tool, no one would know how to use it. Moreover, there are other advantages to it as listed below:
- It provides a quick and easy guide for API consumption.
- Reduces user frustrations.
- Demonstrates useful functions.
- Saves support costs and time.
These are some of the benefits of API documentation. Let us learn and understand what all consists of an API Documentation.
What is an API Documentation?
API documents are similar to a reference manual. It talks about the necessary information of an API, in terms of,
Description of Resources
The data returned by the API are resources. Various endpoints can be used to access resources. Under the same resource, an API will have several routes grouped. Resources and endpoint descriptions are generally short and precise. Additionally, the user guide contains a piece of more detailed information.
Methods and their endpoints
The endpoint is an essential part of the API documentation. The developers implement it to make the requests. Endpoints indicate ways to access resources. The methods indicate permissible interactions such as GET, PUT, POST, or DELETE with the resource. Endpoints, similar to overall resource descriptions, as well have short brief descriptions. The endpoint shows the end path of a resource. It does not include a common base path to all endpoints.
Consider a sample APIs collection available to us as TOOLSQA - Bookstore API provided by TOOLSQA. Please navigate to the URL. Consequently, once the page opens, we will see this:
In the above image:
Bookstore and Account are resources available in Bookstore API
An HTTP method acts as a verb. The listed verbs POST, GET, DELETE in the above image are some of the examples. Moreover, there are others as well, like PUT, PATCH, OPTION, etc.
The routes in the above image are:
- /Account/v1/Authorized
- /Account/v1/GenerateToken
- /Account/v1/User
- /Account/v1/User/{GUID}
- /Account/v1/User/{UserId}
- /BookStore/v1/Books
- /BookStore/v1/Books
The routes are following the method to form an endpoint. Example, GET /Account/v1/Authorized.
Note: We have a very few endpoints listed here in the sample bookstore API URL. But in actual projects, the list of endpoints can be much large.
For an endpoint, the full resource URL needn't be listed in an API document. So, in our example, GET /Account/v1/Authorized is listed instead of GET http://bookstore.toolsqa.com/Account/v1/Authorized
It is done with the thought to make the users focus on the path. Additionally, the user guide provides the full resource URL and the needed authorization in the introductory section.
Parameters used
Parameters are the options that control the resource. There are four kinds of parameters:
- Header parameter: As the name suggests, these are the parameters passed into the request headers. They are generally related to the authorization and included under a separate section of authorization requirements for standard header parameters. Additionally, in the case of unique parameters passed in the headers, they are documented with the respective endpoint.
- Path parameter: Path parameters are part of the endpoint. They are not optional. We usually write them along with curly braces. For example, /Account/v1/User/{UserId} Here, {UserId} is the path parameter.
- Query parameter: They are attached to the URL end. Additionally, the Query Parameter is appended to the URL after adding '?' at the end of the URL. You can read more in the Query Parameter tutorial.
- Request body parameters: In a POST request, several times, a JSON object is submitted in the request body. Moreover, it is a list of key-value pairs.
Example:
{
"username": "TOOLSQA-Test",
"password": "Test@@123"
}
Examples of Requests and Responses
In the above example, we passed the username and password as request body parameters. We got a 200 status code response along with the response body for the POST request of the endpoint /Account/v1/GenerateToken, we sent.
Alongside, if you notice the highlighted section, it is that of a curl command.
curl -X POST "https://bookstore.toolsqa.com/Account/v1/GenerateToken" -H "accept: application/json" -H "authorization: Basic VE9PTFNRQS1UZXN0OlRlc3RAQDEyMw==" -H "Content-Type: application/json" -d "{ \"userName\": \"TOOLSQA-Test\", \"password\": \"Test@@123\"}"
We follow Curl format for several reasons:
- Firstly, curl is independent of the programming language.
- Secondly, it consists of the request header information.
- Additionally, it displays the Request method.
Thus, we have understood the contents of a typical API document. An API document may contain additional information. But the above part of the tutorial gives us a general idea of everything included under an API document. Subsequently, in our next stage, we will learn the usage of an API document.
How to use an API Document?
To understand the use of Swagger API documentation, we will use the same Bookstore API used previously. It consists of a sandbox environment that tests the APIs on the documentation.
Navigate to the Bookstore API. You will see the following:
The grouping of endpoints is as follows:
- Account
- BookStore
Some of those API Endpoints listed below build our End to End Rest API Framework. Please try these examples once you have learned to use the API document at the end of this section.
HTTP VERB Route PURPOSE
POST
/Account/v1/GenerateToken
To generate token as the endpoint name suggests
GET
/Account/v1/User
To create a userId with associated user
GET
/BookStore/v1/Books
To fetch a list of books
POST
/BookStore/v1/Books
To add a book associated with the user
DELETE
/BookStore/v1/Books
To remove a book
GET
/Account/v1/User/{UserId}
To get a list of books associated with userId
We listed some of the APIs below from the bookstore API. They will help us to understand the requests and their response bodies using Swagger.
- To Create User
- To Generate Token with the created User
API Documentation - Create User - POST
Before we begin exploring the APIs, we need to create a user with a username and password. Subsequently, add a desired username and password under Book Store Credentials.
Note: Please choose your username and password, and once created, remember to use the same username and password for all the tests. For example, throughout we will be using TOOLSQA-Test as username and Test@@123 as password.
Now, let's try and create a userID using the POST Request for User.
Make a request
- First, Expand the POST method for the endpoint under Account: /Account/v1/User
- Second, click the Try it out button. The Request Body field gets editable.
- Third, add the username and password. You have added now, as indicated in the below screenshot. After that, select the parameter content type as application/json. Click on the Execute button.
- Finally, Swagger submits the request. It shows the curl that was submitted. The Response, for the request, is observed in the response section.
Note: Please note down the userID. You will need it for other APIs in the request body.
Explanation: We passed a POST Request with username and password in JSON format. We got a 201 status code along with userID, and books associated with the respective username in the Response body. It explains that our request was successfully sent over the server, as you can see in the above image under Description. Additionally, Swagger's implementation is simple and useful. It provides an interactive experience to send a request and receive a response for APIs.
As software professionals, we need to understand the underpinnings on which great software builds. One of the many means is to try and test them over in various ways. We familiarized ourselves with the API Documentation. Additionally, Swagger provided us with the means to test it on Swagger UI itself.
API Documentation - Generate Token - POST
Now that we have created a user in our previous response let's try and create a token using the POST Request for User.
Explanation: We sent our request with the body containing a username and password in the application/json format. When we click the Execute button, we got a 200 status code. Additionally, we also got a response body with the token, expires, status, and result fields. In the next section, we will understand the context of API tests and study its various types.
What are the various types of API Tests?
Before we commence actual testing of the APIs, we need to compile associated information of the APIs. The context in which we carry out testing is essential because it drives our testing efforts.
Below is a non-comprehensive list of the information we can seek before we start testing the APIs:
- Expected response code from a successful request
- Expected response code from an unsuccessful request
- Fields to be validated
- Parameters required
- Error handling of unsuccessful requests
- HTTP verbs that we can use with the endpoints
Let us understand, under which all categories the API tests can be divided. It is not to say that we have enlisted a comprehensive list; there could be more types we can add to the list. But more or less, the API tests fall under one of the categories mentioned below.
- Validation Testing: It forms one of the last segments in the software development process. It caters to the testing explicitly done to assess the product; its behavior, and its efficiency. The API is examined for correctness in implementation as per the expectations. Additionally, the API is also verified based on pre-established agreed-upon criteria such as the delivery of specific end goals, integration with the specified environment. In addition to that, the testing of API's behavior to access the correct data happens as a part of Validation testing.
- Functional Testing: This is one of the broader types of testing. It aims to test specified functions. While testing the APIs for functional testing, the assessment of the responses received happens against expected responses in terms of passing parameters. Additionally, the error handling for unsuccessful requests and invalid responses too are under consideration under these tests. The boundary cases, along with regular tests, fall under the scope of these tests.
- UI Testing: UI Tests tend to be more specific. These tests assess the user interface against the APIs and their constituent parts. Additionally, the tests are more generalized with a focus on the API health; it's usability as well as compatibility of the front end and back end.
- Load Testing: This test is for the functionality and performance of the APIs under load. The API undergoes theoretical regular traffic. The test results form the baseline to conduct further load testing. Additionally, the testing also includes subjecting the API to maximum possible traffic to test the behavior of the API under full loads. APIs undergo overloading tests. In this test, the verification of the API performance and error handling conditions during failure happens.
- Error Detection: The tests for APIs, which include the monitoring, inducing execution errors, sending invalid requests, detecting operational leaks, etc. fall under this category. The introduction of known failure scenarios in the APIs happens. The testing of these APIs happens to ensure that the errors are detected, handled as well as routed.
- API Security tests: Specific tests verify the APIs performance for vulnerabilities from external threats. Security testing, Penetration testing, and Fuzz testing are some of them. For example, Security testing involves validation of the APIs in terms of security requirements. Additionally, these security requirements could be related to permissions, authorizations, authentications, etc. An authorized attack launches against the system as a part of Penetration testing. It evaluates the security of the API. A full-scale assessment report of strengths and weaknesses of the APIs is the deliverable issued after such a test. In Fuzz testing, evaluation of API behavior happens. It is subjected to unexpected, invalid, and random data of enormous size. Following this, the crashes, built-in assertions, and memory leaks are known.
- Integration testing: The Integration tests focus on API communication with another module APIs.
- Reliability testing: The API should display a prompt response for different configurations. It also looks for a response data structure as a part of testing.
To conclude, in this post, we have got an understanding of API documentation. We learned to use the Swagger API document. Additionally, we acquainted ourselves with types of API testing.
In further posts, we will learn to build REST API Automation Framework around the API tests. Try out using the API document as guided above and reach out for your valuable feedback.
In this tutorial, we will learn to write REST API End to End Test.
We carefully laid the foundations of our API automation framework components. We will be heavily relying on our previous knowledge gained for Rest Assured, Cucumber as well as Maven. Subsequently, these will form the base over which we will build our API Automation Framework. I will be referring to them every once in a while and adding useful links as we need more of them. It's advisable to clear up the basics by going through the tutorials:
In our automation framework, we will automate an End to End business flow of a use case. Additionally, it will help to demonstrate the right examples of framework components and explain their usages. Besides, our understanding of building the Rest Assured API Automation Framework from scratch will improve as we go about this tutorial.
Many-a-times we are involved in getting ready-made frameworks that are built by Senior Test Architects or Software Developers. Understanding the underlying principles and design ideas are crucial to develop our skills as software engineers.
Prerequisites for REST API End to End Test
- Java Setup
- IDE Setup
- Maven Setup
- Create a maven project
- Add Rest Assured Dependencies
- Setup Maven Compiler Plugin
- Create a user for the test
Step 1 - Java Setup
We will use Java as our language, for writing our REST API automation framework based on the Rest Assured library. For this, we will need to install Java on our machines if not previously installed. Likewise, please follow through the tutorial to install Java as our first prerequisite.
Step 2 - IDE Setup
As we will be working with Java, we will need an editor to use for Java. Eclipse, IntelliJ, Net Beans, and several others are popular IDEs you can choose to work. Furthermore, a tutorial on installing Eclipse on Windows and for Mac users exists to ease the process. Please pick an IDE you are comfortable working with and get going.
Step 3 - Maven Setup
Build tools enable us to create executable applications from the source code. They help to automate and script everyday activities like downloading dependencies, compiling, running our tests, and deployments. Moreover, we will use the Maven build tool for our End To End Scenarios. We have created a tutorial explaining the installation of Maven on Windows. Additionally, please install Maven, if not previously installed as we would need it.
Step 4: Create a New Maven Project
Eclipse operates with workspaces, folders, and spaces where you add your projects. Software Teams use different approaches for workspaces and project structures but try to follow and stick with the default structure.
In addition to this, to create a new maven project, please follow our article Steps to create a New Maven Project. Just to make sure that you specify below values to Maven Archetype Parameters:
- Group ID: ToolsQA
- Artifact ID: API TestingFramework
Note: You can have any names as Group ID and Artifact ID. However, for the ease of this tutorial, it is better to keep the same naming conventions across the project. Also, it helps in resolving issues while copying pasting the code.
Step 5 - Add Rest Assured Dependencies
We will add Rest Assured Dependencies to our project through the pom.xml file. To add the required dependencies, go to Rest Assured Maven Repository. Then, select the latest dependency. Copy-paste it in the project pom.xml file.
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.2.0</version>
<scope>test</scope>
</dependency>
Note: As of Feb'2020, the latest rest-assured version is 4.2.0.
We should add the dependency tag within the dependencies tag, like below.
<dependencies>
<dependency>Project Dependency</dependency>
</dependencies>
Further, we will be using JUnit Dependency. So please add that dependency in the pom.xml.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
Note: As of Feb'2020, the latest JUnit version is 4.13. Always use the latest Junit version.
Step 6: Setup Maven Compiler Plugin
The Compiler Plugin compiles the sources of the project. Regardless of the JDK you run Maven with, the default source setting is 1.5, and the default target setting is 1.5. Additionally, to change the defaults, please set the source and target as described in Setting the –source and –target of the Java Compiler.
Below you can find the maven-compiler-plugin settings:
<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>
The complete POM would turn out to be likewise:
<?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>RestAssured_APITests</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>
</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>
Step 7: Create an Authorized user for the test
Finally, before we begin automation of our tests, the last piece is remaining. It is of creating an authorized user. We need to create a user that will use our End to End automation tests. We have learned to create a user in our previous tutorial under the POST Request. The POST Request tutorial explains how a POST Request method sends data to the server.
S
@Test
public void RegistrationSuccessful() {
RestAssured.baseURI = "https://bookstore.toolsqa.com";
RequestSpecification request = RestAssured.given();
JSONObject requestParams = new JSONObject();
/*I have put a unique username and password as below,
you can enter any as per your liking. */
requestParams.put("UserName", "TOOLSQA-Test");
requestParams.put("Password", "Test@@123");
request.body(requestParams.toJSONString());
Response response = request.post("/Account/v1/User");
Assert.assertEquals(response.getStatusCode(), 201);
// We will need the userID in the response body for our tests, please save it in a local variable
String userID = response.getBody().jsonPath().getString("userID");
}
Write REST API End to End Test:
Let's consider this scenario:-
Test Scenario: As an existing authorized user, I retrieve a list of books available for me in the library. I will assign a book to myself and later on return it.
We will divide the test scenario into below steps for simplicity:
1. Test will start from generating Token for Authorization - First, we have the username and password of a registered user. Using these credentials, we will generate a token. Additionally, we will send this token into the Requests instead of the username and password. The reason we follow this practice is to have specific resources allocated in a time-bound manner. This step involves making a POST Request call in Rest Assured by passing username and password. Kindly follow this POST Request tutorial to learn about how to send a POST Request.
Note: The below-mentioned User won't work, please create your user for practice.
2. Get List of available books in the library - Secondly, it is a GET Request call. It gets us, the list of available books. Visit the GET Request tutorial to understand the logic of how we make a GET Request call in Rest-Assured.
3. Add a book from the list to the user - The third is a POST Request call. We will send the user and book details in the Request.
4. Delete the added book from the list of books - Fourth is a DELETE Request call. We will delete the added book from the list. The DELETE Request tutorial will help you with it.
5. Confirm if the book removal happens successfully - Last but not least, as a verification step, we will verify if the book has got removed from the user. Therefore, we will send user details in a GET Request call to get details of the user.
Create a Test Package & a Test Class
-
Firstly create a New Package by right click on the src/test/java package and select New >> Package. Moreover, give your package a name apiTests and click Finish.
-
Secondly, create a New Class file by right click on the src/test/java package and select New >> Class. Give your test case the correct name in the resulting dialog and click Finish to create the file. Also, to make sure to check the public static void main, as we will be running the test from the same primary method. I have named the class as E2E_Tests, as you can see in the code snippet below.
Write the complete REST API End to End Test
I have written an end to end test encompassing the steps as mentioned above. The example is relatively simple to follow through. Moreover, it involves the use of GET, POST, and DELETE Requests. It will benefit you if you go through the Rest Assured Tutorial at this point if you are not following.
package apiTests;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import io.restassured.RestAssured;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
public class E2E_Tests {
public static void main(String[] args) {
String userID = "9b5f49ab-eea9-45f4-9d66-bcf56a531b85";
String userName = "TOOLSQA-Test";
String password = "Test@@123";
String baseUrl = "https://bookstore.toolsqa.com";
RestAssured.baseURI = baseUrl;
RequestSpecification request = RestAssured.given();
//Step - 1
//Test will start from generating Token for Authorization
request.header("Content-Type", "application/json");
Response response = request.body("{ \"userName\":\"" + userName + "\", \"password\":\"" + password + "\"}")
.post("/Account/v1/GenerateToken");
Assert.assertEquals(response.getStatusCode(), 200);
String jsonString = response.asString();
Assert.assertTrue(jsonString.contains("token"));
//This token will be used in later requests
String token = JsonPath.from(jsonString).get("token");
//Step - 2
// Get Books - No Auth is required for this.
response = request.get("/BookStore/v1/Books");
Assert.assertEquals(response.getStatusCode(), 200);
jsonString = response.asString();
List<Map<String, String>> books = JsonPath.from(jsonString).get("books");
Assert.assertTrue(books.size() > 0);
//This bookId will be used in later requests, to add the book with respective isbn
String bookId = books.get(0).get("isbn");
//Step - 3
// Add a book - with Auth
//The token we had saved in the variable before from response in Step 1,
//we will be passing in the headers for each of the succeeding request
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
response = request.body("{ \"userId\": \"" + userID + "\", " +
"\"collectionOfIsbns\": [ { \"isbn\": \"" + bookId + "\" } ]}")
.post("/BookStore/v1/Books");
Assert.assertEquals( 201, response.getStatusCode());
//Step - 4
// Delete a book - with Auth
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
response = request.body("{ \"isbn\": \"" + bookId + "\", \"userId\": \"" + userID + "\"}")
.delete("/BookStore/v1/Book");
Assert.assertEquals(204, response.getStatusCode());
//Step - 5
// Get User
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
response = request.get("/Account/v1/User/" + userID);
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. You can read more in the JSONPath article.
Run the REST API Test
Next step, we need to execute the test. Right-click in the test body and select Run As >> Java Application. The test will run, and you'll see the results in the Console. Consequently, the program executes successfully without any error. Just in case you happen to configure the same on IntelliJ IDE, the effect observed will be "Process finished with exit code 0". Additionally, it is another sign signifying that there were no errors and our tests executed successfully.
Note: It is not a UI based test. Moreover, there will not be any visual output to observe the test execution.
Conclusively, in the next tutorial, we will Convert our API tests into Cucumber BDD style. It will further our understanding of the way Cucumber Tests are structured.
In our previous tutorial, we wrote a simple End to End Rest API Test. The business flow of a use case was converted into a simple API request and response format of GET, POST, and DELETE Requests. Subsequently, our next step is to convert the REST API Test in Cucumber. While we are at it, we need to develop an understanding of the Cucumber BDD Framework. The Cucumber tutorials are relatively simple to understand and would be a great primer before we deep dive.
And now, we start with our Rest API Test in Cucumber.
REST API Test in Cucumber BDD Style Test
We will use the Cucumber BDD Framework to execute our tests. Additionally, it would require us to Convert our Rest Assured API Tests to the Cucumber BDD Style Test.
The following steps will help us to achieve this:
- Add Cucumber Dependencies to the Project
- Write a test in a Feature File
- Create a Test Runner
- Write test code to Step file
- Run test as JUnit test & Maven Command Line
Step 1: Add Cucumber Dependencies to the Project
Firstly, add the following dependencies to our project to execute our tests in Cucumber:
- cucumber-java
- cucumber-jvm-deps
- cucumber-JUnit
As we are developing the framework in Maven, it would be useful to add the dependencies. Therefore, add the following dependencies into the project POM XML.
cucumber-java: The location of the dependency details is at the [Maven Repository]tps://mvnrepository.com/artifact/io.cucumber/cucumber-junit/5.2.0).
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>5.2.0</version>
</dependency>
Note: As of Feb’2020, the latest cucumber-java version is 5.2.0.
cucumber-jvm-deps: The location of the dependency details is at the [Maven Repository].(https://mvnrepository.com/artifact/io.cucumber/cucumber-jvm-deps)
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm-deps</artifactId>
<version>1.0.6</version>
<scope>provided</scope>
</dependency>
Note: As of Feb’2020, the latest cucumber-jvm-deps version is 1.0.6
cucumber-junit: The location of the dependency details is at the Maven Repository.
<dependency>
<groupId>io.cucumber<</groupId>
<artifactId>cucumber-junit</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
Note: As of Feb’2020, the latest cucumber-junit version is 5.2.0
Consequently, the complete pom.xml 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>RestAssured_APITests</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>
</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>
Note: Update the POM by right click on the project root and select Maven >> Update Project. This action will nullify the dependencies based issues.
Additional note: If you feel comfortable adding the jars in the project library instead of using Maven dependencies, that's alright as well.
Step 2: Write a test in a Feature File
Secondly, we will highly recommend acquainting yourself with the tutorial on the Feature file. It will help in understanding the basics of the Cucumber feature file. Consequently, we will begin to convert our test scenario into the Cucumber Feature file.
In the previous chapter, we broke down the scenario into parts for us to easily convert the scenario into steps. Let's visit the scenario yet again,
- The test will start by generating a Token for Authorization
- Get List of available books in the library
- Add a book from the list to the user
- Delete the added book from the list of books
- Confirm if the book removal is successful
Conversion of above scenario to Cucumber BDD Style Test:
-
Background: User generates token for Authorisation
Given I am an authorized user
-
Scenario: the Authorized user can Add and Remove a book.
Given A list of books are available
When I add a book to my reading list
Then the book is added
When I remove a book from my reading list
Then the book is removed
Create Feature File
- First, create a New Package and name it as functionalTests. You can do it by right-clicking on the src/test/resources and select New >> Package.
Note: It's always recommendable to put all the feature files in the resources folder.
- Secondly, create a Feature file and name it as End2End_Test.feature by right click on the above created package and select New >> File.
Add .feature extension to the file.
- Lastly, add the test steps to the feature file.
Note: As you are aware, the keywords used in the test steps are in different colors. Those are the Gherkin keywords. Eclipse does not understand these. However, if we install the cucumber Eclipse plugin-in, this will be recognized. Please follow our tutorial to Install Cucumber Eclipse Plugin.
Step 3: Create a JUnit test runner
Thirdly, we will create a Cucumber Test Runner to execute our tests. Moreover, we will need a Cucumber Test Runner based on the JUnit Test Runner for our tests. To understand more about Cucumber Test Runner, please refer JUnit Test Runner for the tutorial.
-
Firstly, Right-click on the src/test/java and select a New Package by using New >> Package. Name it as runners.
-
After that, Create a New Java Class file and name it as TestRunner by right click on the above-created package and select New >> Class.
package runners;
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/functionalTests",
)
public class TestRunner {
}
Note: Do not select the public static void main while creating the runner class.
Step 4: Write test code to Step file
Fourthly, as we move ahead in our next step to convert the test code to Step file, we need to revise our knowledge on Cucumber Step Definitions.
- To reduce our efforts in step creation, we will automatically generate the much required Cucumber Steps for implementation. Consequently, we will execute the TestRunner class for this. Right-click on the TestRunner file and select Run As >> JUnit Test. Therefore, you would get the below result in the Eclipse Console.
-
Create a New Package and title it as stepDefinitions by right click on the src/test/java and select New >> Package.
-
After that, create a New Java Class and name it is as Steps by right click on the above created package and select New >> Class.
-
Finally, copy all the steps created by Eclipse to this Steps file and start filling up these steps with Selenium Code. Select all the code from our Selenium Test file created in the End2End_Test. The Steps test file will look like this:
package stepDefinitions;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
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 USERNAME = "TOOLSQA-Test";
private static final String PASSWORD = "Test@@123";
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() {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
response = request.body("{ \"userName\":\"" + USERNAME + "\", \"password\":\"" + PASSWORD + "\"}")
.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");
Assert.assertTrue(books.size() > 0);
bookId = books.get(0).get("isbn");
}
@When("I add a book to my reading list")
public void addBookInList() {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
response = request.body("{ \"userId\": \"" + USER_ID + "\", " +
"\"collectionOfIsbns\": [ { \"isbn\": \"" + bookId + "\" } ]}")
.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();
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
response = request.body("{ \"isbn\": \"" + bookId + "\", \"userId\": \"" + USER_ID + "\"}")
.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: As compared to our E2E tests created, we have made the following changes:
- Declared the variables as private static final to not allow changes outside the class.
- We have updated method names and not kept the same as auto-generated ones.
- The TestRunner file must be able to find the steps files. To achieve that, we need to mention the path of the package. This path has all of our step definitions in CucumberOptions. Additionally, to know more about the parameters we can add in the @CucumberOptions, please visit our tutorial Cucumber Options.
package runners;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/functionalTests",
glue = {"stepDefinitions"},
monochrome = true,
strict = true
)
public class TestRunner {
}
Note: By default, the Junit/Cucumber finds the test code in the src/test/java folder. Hence, this is why we just need to specify the package name for the cucumber glue.
Step 5: Run the Cucumber Test
Run as JUnit
Finally, we are all set to run the first Cucumber test. Right -Click on TestRunner class and Click Run As >> JUnit Test. Cucumber will execute the script the same way it runs in Selenium WebDriver. Consequently, the result will appear in the left-hand side project explorer window in the JUnit tab.
Run the Tests from Command Prompt
We have a Maven Type project, and thus, we can run our tests from the command prompt as well. A simple command to run tests is an mvn clean test. Moreover, to use this command, we have to change our directory to the location of our Cucumber project. In the below screenshot first, I went to my project location, and then I used the Maven as mentioned above command to run the test.
Consequently, we can see the output of the tests below in the command prompt.
To conclude, by now, we have converted our Rest Assured API tests into Cucumber based tests. Subsequently, we will see how to implement the Java serialization concept in our Framework and enhance it. Moreover, it would be useful at this stage to go through the Convert JSON to JAVA Object tutorial as a refresher before advancing on the next framework tutorial.
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.
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.
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.
In the previous chapter, we introduced the concept of using REST Routes in our Endpoints class for the framework. In case if the route changes, we won't have to make changes everywhere, it's just at a single place in the Routes class.
Subsequently, in this chapter, we are going to implement the Generics concept in our framework. We are dealing with the response objects of various data types such as Token, Books, and User Account. Moreover, one can build these response objects fail-safe by using generics. Generics adds a layer of abstraction. In addition to that, they add a way to specify types for classes and methods. You can read more about the Generics concept in this Generics tutorial. In this article, we are going to cover:-
- What is the need for a Generics class implementation?
- Implementation of Generics
What is the need for a Generics class implementation
Consider an example of returning a Book object from Endpoint method like below:
public static Books getBooks() {
RequestSpecification request = RestAssured.given();
Response response = request.get(Route.books());
return response.getBody().as(Books.class);
}
If we would get a failed response, our code will fail in the above method implementation.
So supposedly if we were to handle the failure like below:
public static Books getBooks() {
RequestSpecification request = RestAssured.given();
Response response = request.get(Route.books());
int code = response.getStatusCode();
if( code == 200 || code == 201 || code == 202 || code == 203 || code == 204 || code == 205) {
return response.getBody().as(Book.Class());
}
}
we would not be able to test negative tests by sending a wrong body and expecting a 204. We would likely be stuck in such a case.
Thus we need a class, which will return the Response Body as well as status and in case of failure return exception or error message.
Now, the response received from the server can be of several data types. Hence, we need an interface capable of handling different response objects. To provide this parameterized value to a parameterized type, we implement this as a Generic Interface. This interface will contain all the methods we need when we operate on a REST Response.
Implementation of Generics
We will follow the steps below to implement Generics in our framework:
- Create a Generic Interface
- Create a class to implement generic interface methods
- Modification of the Endpoints class
- Modification of Steps class
- Run the tests
Create a Generic Interface
Firstly, right-click on the apiEngine package and select New >> Interface. Name it as IRestResponse.
IRestResponse.java
package apiEngine;
import io.restassured.response.Response;
public interface IRestResponse<T>{
public T getBody();
public String getContent();
public int getStatusCode();
public boolean isSuccessful();
public String getStatusDescription();
public Response getResponse();
public Exception getException();
}
Explanation:
We created a generic interface of type <T>
. Thus, we can use this interface to hold responses of different types. For example, IRestResponse<Books>
will hold the response of type Books. Likewise, it will be for IRestResponse<UserAccount>
and IRestResponse<Token>
.
Note: As explained above, we need this interface to return the Body as well as status, and in case of failure return exception or error message. Therefore, that's why we have those attributes in the interface.
Create a class to implement generic interface methods
We defined the methods we need to operate on the REST response. We need to apply these methods next in our Rest Response class.
Right-click on the apiEngine package and select New >> Class. Name it as RestResponse.
package apiEngine;
import io.restassured.response.Response;
public class RestResponse<T> implements IRestResponse<T> {
private T data;
private Response response;
private Exception e;
public RestResponse(Class<T> t, Response response) {
this.response = response;
try{
this.data = t.newInstance();
}catch (Exception e){
throw new RuntimeException("There should be a default constructor in the Response POJO");
}
}
public String getContent() {
return response.getBody().asString();
}
public int getStatusCode() {
return response.getStatusCode();
}
public boolean isSuccessful() {
int code = response.getStatusCode();
if( code == 200 || code == 201 || code == 202 || code == 203 || code == 204 || code == 205) return true;
return false;
}
public String getStatusDescription() {
return response.getStatusLine();
}
public Response getResponse() {
return response;
}
public T getBody() {
try {
data = (T) response.getBody().as(data.getClass());
}catch (Exception e) {
this.e=e;
}
return data;
}
public Exception getException() {
return e;
}
}
Explanation:
We implemented the methods to return us relevant details of REST responses as needed for testing.
- isSuccessful(): Will validate if the sending request was successful. It validates the response status code received against the numerous HTTP status codes denoting that request was successfully processed
- getResponse().getBody().asString(): We will need the response body content in String format at times. This method implementation takes care of it.
- getException(): In case our response body is not deserialized successfully, we will get an exception. e will contain this exception, which we get using this method.
Modification of Endpoints class
Our methods for Endpoints class will change. They will return responses of the type RestResponse<Books>
, RestResponse<Token>
and RestResponse<UserAccount>
for the respective methods.
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");
Response response = request.body(authRequest).post(Route.generateToken());
return response;
}
to
public static IRestResponse<Token> 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 new RestResponse(Token.class, response);
}
Explanation:
In this method, we have wrapped the Rest Assured Response into our RestResponse class of the type Token, where we have deserialized the responses.
Similarly, we updated the below methods for the Endpoints class:
- getBooks()
- addBook()
- removeBook()
- getUserAccount()
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 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 static final String BASE_URL = "https://bookstore.toolsqa.com";
public static IRestResponse<Token> 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 new RestResponse(Token.class, response);
}
public static IRestResponse<Books> getBooks() {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
Response response = request.get(Route.books());
return new RestResponse(Books.class, response);
}
public static IRestResponse<UserAccount> 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 new RestResponse(UserAccount.class, 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");
return request.body(removeBookRequest).delete(Route.book());
}
public static IRestResponse<UserAccount> 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 new RestResponse(UserAccount.class, response);
}
}
Modification of Steps class
We will modify the step definitions to call the methods listed in the endpoints class.
Moreover, you will directly obtain the response in the step definition class. As already explained, the logic of communication with the server and converting it into the response class moves out. Thus, our step definition consists of only the testing layer which we are interested in and not the internal workings of the API.
Our updated step definition file would look like:
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 static final String USER_ID = "9b5f49ab-eea9-45f4-9d66-bcf56a531b85";
private static Response response;
private static Token tokenResponse;
private static IRestResponse<UserAccount> userAccountResponse;
private static Book book;
@Given("^I am an authorized user$")
public void iAmAnAuthorizedUser() {
AuthorizationRequest authRequest = new AuthorizationRequest("TOOLSQA-Test", "Test@@123");
tokenResponse = EndPoints.authenticateUser(authRequest).getBody();
}
@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, tokenResponse.token);
}
@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, tokenResponse.token);
}
@Then("^The book is removed$")
public void bookIsRemoved() {
Assert.assertEquals(204, response.getStatusCode());
userAccountResponse = EndPoints.getUserAccount(USER_ID, tokenResponse.token);
Assert.assertEquals(200, userAccountResponse.getStatusCode());
Assert.assertEquals(0, userAccountResponse.getBody().books.size());
}
}
Run the Cucumber Test
Run the Tests as JUnit
We are all set now to run the updated Cucumber test. Right -Click on TestRunner class and Click Run As >> JUnit Test. Consequently, you will see the result 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.
Our updated project folder structure of the framework will look likewise:
Our tests passed with the changes we made for the Generics implementation in our framework. We will be Refactoring for Request Headers so that we can make use of a single request object in our next chapter. Moreover, it will avoid the complexity of adding the auth header repeatedly for each request.
Meanwhile, please try to implement the above changes in your framework, as explained above.
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.
In our previous chapter, we refactored the endpoints class to pass the Request headers as an instance of Endpoints class instead of passing them in each method. Subsequently, as a next step, we will learn to Share Test Context between our Step Definitions in our API Rest Assured Automation Framework.
Need for Sharing the Test Context between Cucumber Step Definitions
In real-life projects, the expectation is we build a scalable automation framework. Moreover, automation coverage increases as we add multiple scenarios. If a single step definition file contains all the step definitions, it clutters the step definition file. Additionally, it becomes non-maintainable. It is thus, imperative to refactor the step definitions separated under different classes.
In any scenario of a feature file in Cucumber, a series of steps get executed one after one. Every step of the scenario may possess a state which could prove useful to another step in the same scenario. Thus, the steps depend on the previously executed steps. Moreover, for this reason, sharing the state among the steps is needed.
In our framework, we have just one scenario for illustration purposes. But, it will grow over a while as we add numerous scenarios. Additionally, to keep the step definition file uncluttered and share the Test Context with all the Step Definition files, we will resort to the dependency injection Container PicoContainer. We have implemented this before in our former Cucumber tutorial with Selenium on sharing Test Context with Cucumber Step Definitions.
As a next step, we will learn the way to implement the Test Context sharing between our Step Definitions in our API Rest Assured Automation Framework.
How to Share the Test Context between Cucumber Steps using PicoContainer
We will follow the same steps as sharing Test Context with Cucumber Step Definitions to share the data state across steps:
- Firstly, add PicoContainer to the Project.
- Secondly, create a Test Context class to hold all the objects state.
- Thirdly, divide the Steps class into multiple steps classes.
- Fourth, write Constructor to share Test Context.
- Finally, run the Tests.
Add PicoContainer Library to the Maven Project
Add the maven dependency of dependency injection-PicoContainer into our project pom.xml. Additionally, you can find more details about the version etc. at [Maven Repository - Cucumber PicoContainer]
(https://mvnrepository.com/artifact/io.cucumber/cucumber-picocontainer).
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-picocontainer -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
Create a Test Context class which will hold all the objects state
The Text Context class shall encompass all information your Steps files are using. In our framework, we are likewise using the information from the Endpoints class in the Steps file. So simply, we need to add an object of Endpoints class into this Test Context class.
To create a Test Context class,
-
Firstly, Right-click on the src/test/java and select New >> Package. Moreover, name it as a cucumber. All the Cucumber Helper classes will be in the same package hereon.
-
Secondly, Right-click on the above-created package and select New >> Class. Name it as TestContext.
TestContext.java
package cucumber;
import apiEngine.EndPoints;
public class TestContext {
private String BASE_URL = "https://bookstore.toolsqa.com";
private EndPoints endPoints;
public TestContext() {
endPoints = new EndPoints(BASE_URL);
}
public EndPoints getEndPoints() {
return endPoints;
}
}
Consequently, we kept the initialization of Endpoints in the constructor and created getEndPoints() for the object.
Divide the Steps class into multiple steps classes
We can logically divide our endpoints into Account Steps and Book Steps. Some of the necessary steps would include in the BaseStep.
Create three New Classes in the stepDefinitions package with the following names:
- AccountSteps
- BooksSteps
- BaseStep
Note: You may replace the Steps file with BaseSteps during clean up by moving the required step definitions to associated classes. Alternatively, you can create a BaseStep class, and once we completed this tutorial, then delete the Steps file as we no longer would be using it.
Write Constructor to share Test Context
We divided the Step Definitions to different Step classes. These classes commonly share the Endpoints class amongst themselves. Additionally, in real-life projects, we may have such multiple classes that have an extensive requirement for every Step class. Moreover, to create objects for all standard classes using a new operator, again and again, is not advisable.
If we add a constructor to our Steps class file and pass TestContext as a Parameter to the constructor, it would solve all the problems. In the TestContext object, we got everything required for the test.
Thus the AccountsSteps.java class will look as:
package stepDefinitions;
import apiEngine.model.requests.*;
import cucumber.TestContext;
import cucumber.api.java.en.Given;
public class AccountSteps extends BaseStep {
public AccountSteps(TestContext testContext){
super(testContext);
}
@Given("^I am an authorized user$")
public void iAmAnAuthorizedUser() {
AuthorizationRequest authRequest = new AuthorizationRequest("TOOLSQA-Test", "Test@@123");
getEndPoints().authenticateUser(authRequest);
}
}
Likewise, the changes for BooksSteps.java class is:
package stepDefinitions;
import apiEngine.IRestResponse;
import apiEngine.model.Book;
import apiEngine.model.requests.AddBooksRequest;
import apiEngine.model.requests.ISBN;
import apiEngine.model.requests.RemoveBookRequest;
import apiEngine.model.responses.Books;
import apiEngine.model.responses.UserAccount;
import cucumber.TestContext;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import io.restassured.response.Response;
import org.junit.Assert;
public class BooksSteps extends BaseStep {
public BooksSteps(TestContext testContext){
super(testContext);
}
private final String USER_ID = "9b5f49ab-eea9-45f4-9d66-bcf56a531b85";
private Response response;
private IRestResponse<UserAccount> userAccountResponse;
private Book book;
@Given("^A list of books are available$")
public void listOfBooksAreAvailable() {
IRestResponse<Books> booksResponse = getEndPoints().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 = getEndPoints().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 = getEndPoints().removeBook(removeBookRequest);
}
@Then("^The book is removed$")
public void bookIsRemoved() {
Assert.assertEquals(204, response.getStatusCode());
userAccountResponse = getEndPoints().getUserAccount(USER_ID);
Assert.assertEquals(200, userAccountResponse.getStatusCode());
Assert.assertEquals(0, userAccountResponse.getBody().books.size());
}
}
Lastly, the BaseSteps.java class updates as:
package stepDefinitions;
import apiEngine.EndPoints;
import cucumber.TestContext;
public class BaseStep {
private static final String BASE_URL = "https://bookstore.toolsqa.com";
private EndPoints endPoints;
public BaseStep(TestContext testContext) {
endPoints = testContext.getEndPoints();
}
public EndPoints getEndPoints() {
return endPoints;
}
}
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.
The tests passed successfully with the changes we made for sharing the Test Context with Cucumber Step Definitions in our framework. Please try to implement the above changes in your framework, as explained above.
Our updated project folder structure of the framework will look likewise:
In the next chapter, we will introduce the concept of usage of Scenario Context in Framework to share the test data information specifically. Additionally, we will further segregate our steps into the Verification Steps from the Step classes.
In the previous chapter, we implemented a Sharing Test Context between Cucumber Steps. Additionally, we made the changes to make the step definition file uncluttered and share the Test Context with all the Step Definition files. In continuation of the earlier chapter, we will now proceed to Share Data by using Scenario Context in Cucumber.
Scenario Context class holds the test data information explicitly. It helps you store values in a key-value pair between the steps. Moreover, it helps in organizing step definitions better rather than using private variables in step definition classes. We have implemented this before in our previous Cucumber tutorial on sharing data between steps in Cucumber using Scenario Context.
As a next step, we are going to implement the Scenario Context in cucumber for sharing data between our Step Definitions in our API Rest Assured Automation Framework.
Share Scenario Context in Cucumber
In the end-to-end tests, you're interested in verifying the response for the requests sent. Let’s say you wish to check the subsequent actions within the tests:
- Addition of Book
- Removal of Book
In that case, we want to store the book details in Scenario Context object while adding the book to the user account. Likewise, in our next verification step, we remove the book from the user account. Moreover, we retrieve from the ScenarioContext, book details of the added book, and validate its removal from the user account.
We will follow the identical steps as sharing Scenario Context with Cucumber Step Definitions to share the test data information state across steps:
- Firstly, create a Scenario Context class.
- Secondly, save the test information state within the Scenario Context.
- Thirdly, add a Verification Step file to Test for Responses.
- Fourthly, run the Cucumber Test.
Step 1: Create a Scenario Context class
It is a clean code practice to create enums for all the fixed set of values in the project.
-
Firstly, Right-click on the src/test/java and select New >> Package. Moreover, name it as enums. We will keep all the project enums in this package.
-
Secondly, Right-click on the enums package and select New >> Enum. Name it as Context. Moreover, we will add our enum in it.
Context.java
package enums;
public enum Context {
BOOK,
USER_ID,
USER_ACCOUNT_RESPONSE,
BOOK_REMOVE_RESPONSE;
}
- After that, Right-click on the cucumber package and select New >> Class. Name it as ScenarioContext.
package cucumber;
import enums.Context;
import java.util.HashMap;
import java.util.Map;
public class ScenarioContext {
private Map<String, Object> scenarioContext;
public ScenarioContext(){
scenarioContext = new HashMap<String, Object>();
}
public void setContext(Context key, Object value) {
scenarioContext.put(key.toString(), value);
}
public Object getContext(Context key){
return scenarioContext.get(key.toString());
}
public Boolean isContains(Context key){
return scenarioContext.containsKey(key.toString());
}
}
Code Explanation:
- scenarioContext: It's a HashMap object storing the information in the Key-Value pair. The key type is String. Moreover, theValue can be of any Object Type.
- setContext(): This method takes two parameters, key as Context and value as an object. Key is nothing but a Context enum.
- getContext(): This method takes a key as a parameter to returns the object matching the key.
- isContains(): This method checks in the complete Map if the respective key exists or not.
- Fourthly, include ScenarioContext in TextContext. It will ensure that ScenarioContext is shared across all the Cucumber Steps using the Pico-Container library. We added in our previous chapter. Moreover, add a getter method as getScenarioContext() to get the scenarioContext object.
TestContext.java
package cucumber;
import apiEngine.EndPoints;
import enums.Context;
public class TestContext {
private final String BASE_URL = "https://bookstore.toolsqa.com";
private final String USER_ID = "9b5f49ab-eea9-45f4-9d66-bcf56a531b85";
private EndPoints endPoints;
private ScenarioContext scenarioContext;
public TestContext() {
endPoints = new EndPoints(BASE_URL);
scenarioContext = new ScenarioContext();
scenarioContext.setContext(Context.USER_ID, USER_ID);
}
public EndPoints getEndPoints() {
return endPoints;
}
public ScenarioContext getScenarioContext() {
return scenarioContext;
}
}
Also, the BaseSteps.java class updates as:
package stepDefinitions;
import apiEngine.EndPoints;
import cucumber.ScenarioContext;
import cucumber.TestContext;
public class BaseStep {
private EndPoints endPoints;
private ScenarioContext scenarioContext;
public BaseStep(TestContext testContext) {
endPoints = testContext.getEndPoints();
scenarioContext = testContext.getScenarioContext();
}
public EndPoints getEndPoints() {
return endPoints;
}
public ScenarioContext getScenarioContext() {
return scenarioContext;
}
}
Step 2: Save the test information state in the Scenario Context
In this step, we save the responses of our requests to ScenarioContext object. To begin with, we will add the book to the reading list of the user and save our response. Subsequently, the responses will then validate in our assertions as a part of the verification steps.
In the below steps of the BooksSteps.java class, we are saving responses to scenario context objects.
BooksSteps.java
package stepDefinitions;
import apiEngine.IRestResponse;
import apiEngine.model.Book;
import apiEngine.model.requests.AddBooksRequest;
import apiEngine.model.requests.ISBN;
import apiEngine.model.requests.RemoveBookRequest;
import apiEngine.model.responses.Books;
import apiEngine.model.responses.UserAccount;
import cucumber.TestContext;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.When;
import enums.Context;
import io.restassured.response.Response;
public class BooksSteps extends BaseStep {
public BooksSteps(TestContext testContext) {
super(testContext);
}
@Given("^A list of books are available$")
public void listOfBooksAreAvailable() {
IRestResponse<Books> booksResponse = getEndpoints().getBooks();
Book book = booksResponse.getBody().books.get(0);
getScenarioContext().setContext(Context.BOOK, book);
}
@When("^I add a book to my reading list$")
public void addBookInList() {
Book book = (Book) getScenarioContext().getContext(Context.BOOK);
String userId = (String) getScenarioContext().getContext(Context.USER_ID);
ISBN isbn = new ISBN(book.isbn);
AddBooksRequest addBooksRequest = new AddBooksRequest(userId, isbn);
IRestResponse<UserAccount> userAccountResponse = getEndpoints().addBook(addBooksRequest);
getScenarioContext().setContext(Context.USER_ACCOUNT_RESPONSE, userAccountResponse);
}
@When("^I remove a book from my reading list$")
public void removeBookFromList() {
Book book = (Book) getScenarioContext().getContext(Context.BOOK);
String userId = (String) getScenarioContext().getContext(Context.USER_ID);
RemoveBookRequest removeBookRequest = new RemoveBookRequest(userId, book.isbn);
Response response = getEndpoints().removeBook(removeBookRequest);
getScenarioContext().setContext(Context.BOOK_REMOVE_RESPONSE, response);
}
}
Step 3: Add a Verification Step file to Test for Responses
We will verify the responses saved in our ScenarioContext Objects as a part of this step. We will move all our verification steps to a new class created as VerificationSteps.java.
Create a New Class and name it as VerificationSteps, by right-clicking on the stepDefinitions. After that, select New >> Class.
VerificationSteps.java
package stepDefinitions;
import apiEngine.IRestResponse;
import apiEngine.model.Book;
import apiEngine.model.responses.UserAccount;
import cucumber.TestContext;
import cucumber.api.java.en.Then;
import enums.Context;
import io.restassured.response.Response;
import org.junit.Assert;
public class VerificationSteps extends BaseStep {
public VerificationSteps(TestContext testContext) {
super(testContext);
}
@Then("^The book is added$")
public void bookIsAdded() {
Book book = (Book) getScenarioContext().getContext(Context.BOOK);
String userId = (String) getScenarioContext().getContext(Context.USER_ID);
IRestResponse<UserAccount> userAccountResponse = (IRestResponse<UserAccount>) getScenarioContext().getContext(Context.USER_ACCOUNT_RESPONSE);
Assert.assertTrue(userAccountResponse.isSuccessful());
Assert.assertEquals(201, userAccountResponse.getStatusCode());
Assert.assertEquals(userId, userAccountResponse.getBody().userID);
Assert.assertEquals(book.isbn, userAccountResponse.getBody().books.get(0).isbn);
}
@Then("^The book is removed$")
public void bookIsRemoved() {
String userId = (String) getScenarioContext().getContext(Context.USER_ID);
Response response = (Response) getScenarioContext().getContext(Context.BOOK_REMOVE_RESPONSE);
Assert.assertEquals(204, response.getStatusCode());
IRestResponse<UserAccount> userAccountResponse = getEndPoints().getUserAccount(userId);
Assert.assertEquals(200, userAccountResponse.getStatusCode());
Assert.assertEquals(0, userAccountResponse.getBody().books.size());
}
}
Explanation
- We just need to pass the Key to the getContext() method, which we can access like scenarioContext.getContext(Key Name). Additionally, it will retrieve the value returned by the response for Book.
- Since the method returns an object, we need to cast it to the right type. If it's cast it to a wrong type, the test will fail here. So, we must be sure of the object type we stored for the respective key.
Step 4: Run the Cucumber 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 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. Select the Run As>>Cucumber Feature.
The tests passed successfully with the changes we made for sharing test data in Cucumber Steps using Scenario Context in our framework.
Our updated project folder structure of the framework will look likewise:
In the next chapter, we will add Config Reader class for BaseUrl and UserId. Please try to implement the above changes in your framework, as explained above.
In our previous chapter, we accomplished the sharing of test data in Cucumber Steps using Scenario Context. Subsequently, in this cImplement Configuration Readerhapter, we shall read the configuration values from a property file and use them in our code with the help of Configuration Reader.
If you may have noticed, so far, we are passing the user_Idand the base_Url values as hardcoded ones. However, the problem with hardcoded values is that they are non-maintainable. In other words, the changes in configuration value amount to making changes in several places in the code. Therefore, it is not a clean code practice.
As a solution to this, we will go for property file implementation in Java. If you wish to understand this on a conceptual level, you could refer to the Read Configurations from Property File. Let us learn how to implement the configurations from the property file.
Implement Configuration Reader to Read Project Configurations
We will follow the below steps and implement Read Configurations:
- How to Read Configurations from Property File
- Write Hard-Coded Values in the Property File
- Create a Configuration Reader File
- Use ConfigFileReader object in the TestContext file
- Run the Cucumber Test
How to Read Configurations from Property File
Firstly, right-click on the root Project and select New >> Folder. Additionally, name it as configs. Moreover, we will keep config files with in the same folder.
Secondly, right-click the above-created folder and select New >> File. Additionally, name it as configuration.properties. Here, the .properties is the file extension.
Step 2: Write Hard-Coded Values to Property File
If we look at our TestContext.java class, we have been using two hardcoded values:
base_Url = http://bookstore.toolsqa.com
user_Id = 9b5f49ab-eea9-45f4-9d66-bcf56a531b85
In the configuration.properties file, we will move these values in key-pair format.
Step 3: Create a Config File Reader
Firstly, right-click on the src/test/java and select New >> Package. In addition to that, name it as dataProvider. Moreover, all the data-readers files will be kept here in this package.
Secondly, right-click on the above created package and select New >> Class. After that, name it as ConfigFileReader.
ConfigReader.java
package configs;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class ConfigReader {
private Properties properties;
private static ConfigReader configReader;
private ConfigReader() {
BufferedReader reader;
String propertyFilePath = "configs//Configuration.properties";
try {
reader = new BufferedReader(new FileReader(propertyFilePath));
properties = new Properties();
try {
properties.load(reader);
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("Configuration.properties not found at " + propertyFilePath);
}
}
public static ConfigReader getInstance( ) {
if(configReader == null) {
configReader = new ConfigReader();
}
return configReader;
}
public String getBaseUrl() {
String baseUrl = properties.getProperty("base_Url");
if(baseUrl != null) return baseUrl;
else throw new RuntimeException("base_Url not specified in the Configuration.properties file.");
}
public String getUserID() {
String userId = properties.getProperty("user_Id");
if(userId != null) return userId;
else throw new RuntimeException("user_Id not specified in the Configuration.properties file.");
}
}
Code Explanation:
How to Load Property File
BufferedReader reader = new BufferedReader(new FileReader(propertyFilePath));
Properties properties = new Properties();
properties.load(reader);
- propertyFilePath: It would be a String variable containing the information of the config file path.
- new FileReader(propertyFilePath): Creates a new FileReader containing file-name, to be read from.
- new BufferedReader(new FileReader(propertyFilePath)): Reads the text from a character-input stream. Moreover, it ensures efficient reading of the characters, arrays, and lines by buffering the characters.
- new Properties(): The Properties class represents a persistent set of properties. which can be saved to a stream or loaded from it. Additionally, the key and the corresponding value in the property list is a string.
- properties.load(reader): It reads a property list of key and element pairs from the input character stream in a simple line-oriented format.
If you have noticed the class ConfigReader.java, we have implemented it as a singleton class. The Singleton class’s purpose is to control object creation, limiting the number of objects to only one. Additionally, there is only one Singleton instance. Therefore, any instance fields of a Singleton would occur only once per class, similar to static fields.
Why do we need Singleton class implementation?
Singleton pattern ensures the creation of only one instance of a class in the JVM. Moreover, it enables a global point of access to the object. In our case, we have ConfigReader.java, which should be accessed globally. So it is better to make the ConfigReader.java class as a singleton.
How to implement a Singleton Pattern?
The common concepts among several approaches to implement a Singleton pattern are:
- Private constructor
- Static field containing only it's an instance
- A public static method returning an instance of the class
public class ConfigReader {
// The Static member holds a single instance of the
// ConfigReader class
private static ConfigReader configReader;
// ConfigReader prevents any other class from instantiating
private ConfigReader() {
}
// Provides Global point of access
public static ConfigReader getInstance() {
if (configReader == null) {
configReader = new ConfigReader();
}
return configReader;
}
}
The ConfigReader.java class maintains a static reference to its instance. Additionally, it returns that reference from the static getInstance() method.
ConfigReader.java implements a private constructor so clients cannot instantiate ConfigReader instances.
ConfigFileReader Method
public String getBaseUrl() {
String baseUrl = properties.getProperty("base_Url");
if(baseUrl != null) return baseUrl;
else throw new RuntimeException("base_Url not specified in the configuration.properties file.");
}
properties.getBaseUrl(): Properties object gives us a .getProperty method. Additionally, the input is Key of the property sent as a parameter while the Value of the matched key is the output from the .properties file.
Moreover, if the properties file doesn't have the specified key, it returns the null. Hence, we have to put the null check and, in case of null, throw an exception to stop the test with stack trace information.
For more details, you can visit the article on Singleton Pattern in Java.
Step 4: Use ConfigFileReader object in the TestContext file
After making the necessary changes for the BASE_URL and USER_ID inTextContext.java class:
private final String BASE_URL = "https://bookstore.toolsqa.com";
private EndPoints endPoints;
public TestContext() {
endPoints = new EndPoints(BASE_URL);
scenarioContext = new ScenarioContext();
scenarioContext.setContext(Context.USER_ID, USER_ID);
}
Consequently, the TextContext.java class transforms to:
package cucumber;
import apiEngine.EndPoints;
import configs.ConfigReader;
import enums.Context;
public class TestContext {
private EndPoints endPoints = new EndPoints(ConfigReader.getInstance().getBaseUrl());
private ScenarioContext scenarioContext;
public TestContext() {
scenarioContext = new ScenarioContext();
scenarioContext.setContext(Context.USER_ID, ConfigReader.getInstance().getUserID());
}
public EndPoints getEndPoints() {
return endPoints;
}
public ScenarioContext getScenarioContext() {
return scenarioContext;
}
}
Step 5: Run the Cucumber 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 runs the script in the same way as Selenium WebDriver. 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. Select the Run As>>Cucumber Feature.
Moreover, please try to implement the above changes in your framework, as explained above. Subsequently, our updated project folder structure of the framework will look likewise:
The tests passed successfully with the changes we made to the Configuration Reader in our framework.
We have seen all the types of HTTP requests so far with respect to Rest Assured. Since these requests are distributed in various tutorials, a collective reference can serve as a bookmark for our readers as they get their hands dirty with rest assured. In this short post, we have combined rest assured examples with various HTTP requests with hands-on code for each of them. This is also reflected in the index as below:
- Rest Assured examples with HTTP API Requests
- HTTP GET Request Implementation
- POST Request Implementation
- HTTP PUT Request Implementation
- HTTP DELETE Request Implementation
REST assured examples with HTTP API Requests
Rest Assured API supports functionality for various HTTP Requests like GET, POST, PUT, DELETE. Let us briefly discuss each of these methods and also walkthrough implementation of each method in Rest Assured.
HTTP GET Request Implementation
The HTTP GET request is used widely in the API world. We can use this request to fetch/get a resource from a server without sending any data with our requests. Since we are focusing on examples only in this post, you can refer to the GET request post here.
Let us now implement the GET request using Rest Assured. The code for the same is shown below.
import io.restassured.RestAssured;
import io.restassured.http.Method;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
public class RestAssuredAPITest {
@Test
public void GetBooksDetails() {
// Specify the base URL to the RESTful web service
RestAssured.baseURI = "https://demoqa.com/BookStore/v1/Books";
// Get the RequestSpecification of the request to be sent to the server.
RequestSpecification httpRequest = RestAssured.given();
// specify the method type (GET) and the parameters if any.
//In this case the request does not take any parameters
Response response = httpRequest.request(Method.GET, "");
// Print the status and message body of the response received from the server
System.out.println("Status received => " + response.getStatusLine());
System.out.println("Response=>" + response.prettyPrint());
}
}
In the above code, we send a GET request to fetch the list of books and details of each book.
POST Request Implementation
Suppose we want to post/send some data on the server or create a resource on the server, then we go for an HTTP POST request. For more information on the same, you can refer to Understanding HTTP POST Request Method using Rest Assured for more details.
The following code implements the HTTP POST request using Rest Assured.
public void postRequestBooksAPI()
{
RestAssured.baseURI = "https://demoqa.com";
RequestSpecification request = RestAssured.given();
// JSONObject is a class that represents a Simple JSON.
// We can add Key - Value pairs using the put method
JSONObject requestParams = new JSONObject();
requestParams.put("userId", "TQ123");
requestParams.put("isbn", "9781449325862");
// Add a header stating the Request body is a JSON
request.header("Content-Type", "application/json"); // Add the Json to the body of the request
request.body(requestParams.toJSONString()); // Post the request and check the response
Response response = request.post("/BookStore/V1/Books");
System.out.println("The status received: " + response.statusLine());
}
Here we post a request to BookStore and get the response from the API.
HTTP PUT Request Implementation - Rest Assured Examples
The HTTP PUT request either update a resource or substitutes the representation of the target resource with the full JSON request payload. For a detailed discussion on HTTP PUT requests, visit PUT Request using Rest Assured.
The code below makes a PUT request using Rest Assured.
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification;
public class UpdateBook {
String userId= "toolsqa_test";
String baseUrl="https://demoqa.com";
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6InRlc3RpbmcxMjMiLCJwYXNzd29yZCI6IlBhc3N3b3JkQDEiLCJpYXQiOjE2Mjg1NjQyMjF9.lW8JJvJF7jKebbqPiHOBGtCAus8D9Nv1BK6IoIIMJQ4";
String isbn ="9781449325865";
@Test
public void updateBook() {
RestAssured.baseURI = baseUrl;
RequestSpecification httpRequest = RestAssured.given().header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
//Calling the Delete API with request body
Response res = httpRequest.body("{ \"isbn\": \"" + isbn + "\", \"userId\": \"" + userId + "\"}").put("/BookStore/v1/Book/9781449325862");
//Fetching the response code from the request and validating the same
System.out.println("The response code - " +res.getStatusCode());
Assert.assertEquals(res.getStatusCode(),200);
}
}
Here, we update the book record with the given ISBN value by setting a new ISBN value and also setting the userId value.
HTTP DELETE Request Implementation
We can delete a resource from a server by making a DELETE request. We have a detailed description of the DELETE request in the article, DELETE Request using Rest Assured.
The following Rest assured example demonstrates the working of HTTP DELETE Request.
package bookstore;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification;
public class DeleteBook {
String userId= "de5d75d1-59b4-487e-b632-f18bc0665c0d";
String baseUrl="https://demoqa.com";
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6InRlc3RpbmcxMjMiLCJwYXNzd29yZCI6IlBhc3N3b3JkQDEiLCJpYXQiOjE2Mjg1NjQyMjF9.lW8JJvJF7jKebbqPiHOBGtCAus8D9Nv1BK6IoIIMJQ4";
String isbn ="9781449337711";
@Test
public void deleteBook() {
RestAssured.baseURI = baseUrl;
RequestSpecification httpRequest = RestAssured.given().header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
//Calling the Delete API with request body
Response res = httpRequest.body("{ \"isbn\": \"" + isbn + "\", \"userId\": \"" + userId + "\"}").delete("/BookStore/v1/Book");
//Fetching the response code from the request and validating the same
System.out.println("The response code is - " +res.getStatusCode());
Assert.assertEquals(res.getStatusCode(),204);
}
}
The above code is for the HTTP DELETE request. Here we pass the ISBN and the userId for which the resource is to be deleted. The response obtained confirms the deletion (or if not).
Key TakeAways
In this article, we have presented programming examples of various HTTP requests using the REST Assured library. You can go through the details of each of the requests using the link provided in the description of each method above.
- HTTP GET request is to fetch a particular resource from the server.
- The HTTP POST request posts or sends information or create a new resource on the server.
- HTTP PUT request updates a particular resource or substitutes the representation of the target resource.
- An HTTP DELETE request deletes a particular resource from the server.
Each of the above methods can be programmatically simulated using Rest Assured.