Run Gradle Cucumber Tests from Command Line

HOME

The implementation of a test framework is considered successful and effective, if the test framework supports test execution in multiple ways.
The tests in a Gradle Cucumber Framework can be executed as JUnit Tests, Gradle Tests and Gradle commands from Command Line.

In this tutorial, I will explain to run Gradle tests from Command Line.

Versions Used

  1. Cucumber – 7.5.0
  2. Java – 11
  3. JUnit – 4.13.2
  4. Rest Assured – 4.3.3

To execute tests using JUnit Tests and Gradle Tests, we need to create a JUnit TestRunner.

Steps to follow

  1. Create a Gradle Java Project.
  2. Add Rest-Assured and Cucumber dependencies to the Gradle project
  3. Add Configuration to build.gradle
  4. Add Gradle Cucumber Task to build.gradle
  5. Create a feature file under src/test/resources
  6. Create the Step Definition class or Glue Code for the Test Scenario
  7. Run the tests from Command Line

Step 1 – Create a Gradle project

Step 2 – Add the below-mentioned dependencies in the Gradle project in build.gradle.

plugins {
    // Apply the java-library plugin to add support for Java Library

    id 'java-library'
 
}

repositories {
    // Use jcenter for resolving dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
    mavenCentral()
}

dependencies {
    // This dependency is exported to consumers, that is to say found on their compile classpath.
    api 'org.apache.commons:commons-math3:3.6.1'

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
    implementation 'com.google.guava:guava:29.0-jre'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.13'
    testImplementation 'io.cucumber:cucumber-java:6.6.1'
    testImplementation 'io.cucumber:cucumber-junit:6.6.1'
    testImplementation 'io.rest-assured:rest-assured:4.3.3'
}

Step 3Add Configuration to build.gradle

configurations {
    cucumberRuntime {
        extendsFrom testImplementation
    }
}

Step 4Add Gradle Cucumber Task to build.gradle

task cucumber() {
    dependsOn assemble, testClasses
    doLast {
        javaexec {
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
            args = ['--plugin', 'pretty','--glue', 'Cucumber_Gradle_Demo.definitions', 'src/test/resources']
        }
    }
}

task getexample() {
    dependsOn assemble, compileTestJava
    doLast {
        javaexec {
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
            args = ['--plugin', 'pretty', '--glue', 'Cucumber_Gradle_Demo.definitions', 'src/test/resources/features/', '--tags', '@getexample']
        }
    }
}

task postexample() {
    dependsOn assemble, compileTestJava
    doLast {
        javaexec {
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
            args = ['--plugin', 'pretty', '--glue', 'Cucumber_Gradle_Demo.definitions', 'src/test/resources/features/', '--tags', '@postexample']
        }
    }
}

Here, task cucumber will execute all the tests present in the project irrespective of the number of feature files and scenarios within the feature file.

Step 5 Create a feature file under src/test/resources

I have created 2 sample feature files – API_GetExample.feature and API_PostExample.feature.

Below is the API_GetExample.feature

@getexample
Feature: Validation of get method
 
  @GetUserDetails
  Scenario Outline: Send a valid Request to get user details
 
  Given I send a request to the URL to get user details
  Then the response will return status 200 and id <id> and salary <employee_salary> and name "<employee_name>" and age <employee_age> and message "<message>"
 
Examples:
    |id  |employee_salary|employee_name |employee_age  |message                                  |
    |1   |320800         |Tiger Nixon   |61            |Successfully! Record has been fetched.   |
    
      
  @GetAllUsers    
  Scenario Outline: Send a valid Request to get the details of all the users
 
  Given I send a request to the URL to get the details of all the users
  Then the response will return status 200 and message "<message>"
 
Examples:
      |message                                  |
      | Successfully! All records has been fetched.   |

API_PostExample.feature

@postexample
Feature: Validation of POST method
 
  @CreateUser
  Scenario Outline: Send Request to create a user
 
  Given I send a request to the URL to create a new user
  Then the response will return status 200 and name "<employee_name>" and message "<message>"
 
Examples:
    |employee_name |message                                |
    |posttest      |Successfully! Record has been added.   |

1. Run Test from Command Line

1. Open the command prompt and change the directory to the project location.

cd C:\Users\Vibha\Projects\Vibha_Personal\Cucumber_Gradle_Demo

2. All feature files should be in src/test/resources and create the Cucumber Runner class as CucumberRunnerTest.
Note:- The Runner class name should end with Test to execute the tests from Command Line

2. Running all Feature files or Tests from Command Line

The below command will run all the tests present in the project. As you can see, there are 2 feature files – API_GetExample.feature contains 2 scenarios, and API_PostExample.feature contains 1 scenario.

gradle cucumber

The below screenshot shows that Task: Cucumber is triggered.

The below screenshot shows that the tests are executed and the status of the tests.

3. Running a Feature file from Command Line

To run a particular feature, create a task – postexample for that feature in the build.gradle as shown in the below example.

task postexample() {
    dependsOn assemble, compileTestJava
    doLast {
        javaexec {
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
            args = ['--plugin', 'pretty', '--glue', 'Cucumber_Gradle_Demo.definitions', 'src/test/resources/features/', '--tags', '@postexample']
        }
    }
}

Add this task as a feature tag name and use it to run the test of that particular feature file.

@postexample
Feature: Validation of POST method

Use the below command to run the tests of API_PostExample.feature.

gradle postexample

4. Running Scenarios using Tags from Command Line

To execute the tests using tags, we need to add ‘–tags’, “${tags}” in build.gradle

task cucumber() {
    dependsOn assemble, testClasses
    doLast {
        javaexec {
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
            args = ['--plugin', 'pretty','--tags', "${tags}",'--glue', 'Cucumber_Gradle_Demo.definitions', 'src/test/resources']
        }
    }
}

Use the below-mentioned command to run the tests tagged with tag = GetUserDetails.

gradle cucumber -P tags=@GetUserDetails

5. Running a group of tests from Command Line

Below is the feature file

@getexample
Feature: Validation of get method
 
  @GetUserDetails @SmokeTest
  Scenario Outline: Send a valid Request to get a user details
 
  Given I send a request to the URL to get user details
  Then the response will return status 200 and id <id> and salary <employee_salary> and name "<employee_name>" and age <employee_age> and message "<message>"
 
Examples:
    |id  |employee_salary|employee_name |employee_age  |message                                  |
    |1   |320800         |Tiger Nixon   |61            |Successfully! Record has been fetched.   |
         
  @GetAllUsers 
  Scenario Outline: Send a valid Request to get the details of all the users
 
  Given I send a request to the URL to get the details of all the users
  Then the response will return status 200 and message "<message>"
 
Examples:
    |message                                        |
    | Successfully! All records has been fetched.   |
      
      
  @GetInvalidUsers @SmokeTest
  Scenario Outline: Send a valid Request to get the details of the invalid users
 
  Given I send a request to the URL to get the details of the invalid user
  Then the response will return status 200 and message "<message>"
 
Examples:
    | message                                  |
    | Successfully! Record has been fetched.   |   
      
  @GetInvalidUsers
  Scenario Outline: Send a valid Request to get the details of the user with id 0
 
  Given I send a request to the URL to get the details of the user with id 0
  Then the response will return status 200 and message "<message>" and error "<errorMessage>"
 
Examples:
    | message             | errorMessage  |
    | Not found record.   | id is empty   |  
        
  @Test3
  Scenario: Test 3
     
  Given I send a request to the URL to get the details of the user3
  Then the response will return successfully
  
  
  @Test4 
  Scenario: Test 4
     
  Given I send a request to the URL to get the details of the user4
  Then the response will return successfully
  

I want to run 2 tests – @GetUserDetails and @GetInvalidUsers. I can create a task with the name of @SmokeTest and assign these scenarios wit h the same tag. The task will look like as shown below:

task smokeTest() {
    dependsOn assemble, compileTestJava
    doLast {
        javaexec {
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
            args = ['--plugin', 'pretty', '--glue', 'com.example.definitions', 'src/test/resources/features/', '--tags', '@SmokeTest']
        }
    }
        
}

Use the below-mentioned command to run the tests.

gradle cucumber -P tags=@SmokeTest

The output of the above program is

6. Skipping the execution of a group of tests from Command Line

In the below feature file, I have marked 4 tests as “@Ignore” and 2 are valid.

Feature: Validation of get method
 
  @GetUserDetails @SmokeTest
  Scenario Outline: Send a valid Request to get a user details
 
  Given I send a request to the URL to get user details
  Then the response will return status 200 and id <id> and salary <employee_salary> and name "<employee_name>" and age <employee_age> and message "<message>"
 
Examples:
    |id  |employee_salary|employee_name |employee_age  |message                                  |
    |1   |320800         |Tiger Nixon   |61            |Successfully! Record has been fetched.   |
    
      
  @GetAllUsers @Ignore
  Scenario Outline: Send a valid Request to get the details of all the users
 
  Given I send a request to the URL to get the details of all the users
  Then the response will return status 200 and message "<message>"
 
Examples:
    |message                                        |
    | Successfully! All records has been fetched.   |
      
      
  @GetInvalidUsers @SmokeTest @Ignore
  Scenario Outline: Send a valid Request to get the details of the invalid users
 
  Given I send a request to the URL to get the details of the invalid user
  Then the response will return status 200 and message "<message>"
 
Examples:
    | message                                  |
    | Successfully! Record has been fetched.   |   
      
  @GetInvalidUsers @Ignore
  Scenario Outline: Send a valid Request to get the details of the user with id 0
 
  Given I send a request to the URL to get the details of the user with id 0
  Then the response will return status 200 and message "<message>" and error "<errorMessage>"
 
Examples:
    | message             | errorMessage  |
    | Not found record.   | id is empty   |  
        
  @Test3 @Ignore
  Scenario: Test 3
     
  Given I send a request to the URL to get the details of the user3
  Then the response will return successfully
  
  
  @Test4 
  Scenario: Test 4
     
  Given I send a request to the URL to get the details of the user4
  Then the response will return successfully
     

Add the below-mentioned tag in the build.gradle.

def tags = (findProperty('tags') == null) ? 'not @Ignore' : findProperty('tags') + ' and not @Ignore'

Use the below-mentioned command to run the tests.

gradle cucumber 

The program will execute only 2 tests and will skip the rest 4 tests. The output of the above program is

If I use the tag @SmokeTest here in the command line, then it will run all the tests tagged with @SmokeTest, but will ignore the tests tagged with @Ignore. So, in this case, it will run only 1 test – @GetUserDetails.

gradle cucumber -P tags=@SmokeTest

7. Run multiple tags

Add the below code to build.gradle

test {
    systemProperty "cucumber.filter.tags", System.getProperty("cucumber.filter.tags")
     testLogging {
     showStandardStreams = true
 }
}

This will help us to run multiple tags in cucumber. (Cucumber6 and above uses, cucumber.filter.tags , so for the lower version use cucumber.options).

Use the below command to run 2 tests in gradle.

gradle test -Dcucumber.filter.tags="@GetUserDetails or @GetAllUsers"

or means that test tagged with either of these tags can be run. So, here 2 tests should run.

Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!! Cheers!!

How To Create Gradle Project with Cucumber to test Rest API

HOME

This tutorial describes the creation of the Gradle Java Project to test Rest API using Cucumber BDD and Rest-Assured.

In this tutorial, I will explain creating a framework for the testing of Rest API in Cucumber BDD.

  1. Cucumber – 6.8.1 or above
  2. Java 8 or above
  3. JUnit 4
  4. Gradle 6.6.1 (Build Tool)
  5. Rest Assured 4.3.3

Project Structure

Step 1- Download and Install Java

Cucumber and Rest-Assured need Java to be installed on the system to run the tests. Click here to learn How to install Java.

Step 2 – Download and setup Eclipse IDE on the system

The Eclipse IDE (integrated development environment) provides strong support for Java developers. Click here to learn How to install Eclipse.

Step 3 – Setup Gradle

To build a test framework, we need to add several dependencies to the project. This can be achieved by any build tool. I have used the Gradle Build Tool. Click here to learn How to install Gradle.

Step 4 – Create a new Gradle Project

To create a new Gradle project, go to the top left side and select File -> New Project -> Gradle -> Gradle project -> Next -> Enter Project Name and Project Location ->Next ->Select Gradle Version ->Next ->Review the Configuration -> Finish.

Click here to know How to create a Gradle Java project. Below is the structure of the Gradle project.

Step 5 – Add Rest-Assured and Cucumber dependencies to the Gradle project

This syntax is used for Gradle 5.0 and higher.

dependencies {
    // This dependency is exported to consumers, that is to say found on their compile classpath.
    api 'org.apache.commons:commons-math3:3.6.1'

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
    implementation 'com.google.guava:guava:29.0-jre'
    
    testImplementation 'io.cucumber:cucumber-java:6.8.1'
    testImplementation 'io.cucumber:cucumber-junit:6.8.1'
    testImplementation 'io.rest-assured:rest-assured:4.3.3'

If you are using Gradle 4.10.3 or older, use the below dependency block to build.gradle.

dependencies {
    testCompile 'io.cucumber:cucumber-java:6.8.1'
}

Step 6 – Add Configuration to build.gradle

The below configuration is added to the build.gradle when Gradle is 5.0 or higher version.

configurations {
    cucumberRuntime {
        extendsFrom testImplementation
    }
}

If Gradle is 4.10.3 or older, use the below configuration.

configurations {
    cucumberRuntime {
        extendsFrom testRuntime
    }
}

Step 7 – Add Gradle Cucumber Task to build.gradle 

task cucumber() {
    dependsOn assemble, testClasses
    doLast {
        javaexec {
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
            args = ['--plugin', 'pretty', '--glue', 'com.example.gradle.apidemo', 'src/test/resources']
        }
    }
}

Once you have added dependencies, configurations, and Gradle cucumber task, Right-click on the project, Hover to the Gradle option, and click Refresh Gradle Project. Eclipse does not automatically update the classpath of the build.gradle file is updated. Select Gradle  Refresh Gradle Project from the context menu of the project or from your build.gradle file for that.

Step 8 – Create a feature file under src/test/resources

A new Gradle Project is created with 4 folders – src/main/java, src/main/resources, src/test/java and src/test/resources. Features are created under the src/test/resources directory. Create a folder with name features. Now, create the feature file in this folder. The feature file should be saved with the extension .feature. This feature file contains the test scenarios created to test the application. The Test Scenarios are written in Gherkins language in the format of Given, When, Then, And, But.

Below is an example of a Test Scenario where we are using the GET method to get the information from the API.

Feature: Validation of get method
 
@GetUserDetails
  Scenario Outline: Send a valid Request to get user details
 
  Given I send a request to the URL to get user details
  Then the response will return status 200 and id <id> and salary <employee_salary> and name "<employee_name>" and age <employee_age> and message "<message>"
 
Examples:
    |id  |employee_salary|employee_name |employee_age  |message                                  |
    |1   |320800         |Tiger Nixon   |61            |Successfully! Record has been fetched.   |
   

Step 9 – Create the Step Definition class or Glue Code for the Test Scenario

Step Definition acts as an intermediate to your runner and feature file. It stores the mapping between each step of the scenario in the Feature file. So when you run the scenario, it will scan the step definition file to check the matched glue or test code.

import io.restassured.http.ContentType;
 
import io.restassured.response.ValidatableResponse;
 
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
 
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
 
public class API_GETDefinitions {
     
 
    private ValidatableResponse validatableResponse;
 
    private String endpoint = "http://dummy.restapiexample.com/api/v1/employee/1";
  
    @Given("I send a request to the URL to get user details")
    public void sendRequest(){
        validatableResponse = given().contentType(ContentType.JSON)
                .when().get(endpoint).then();   
         
        System.out.println("Response :"+validatableResponse.extract().asPrettyString());
    }
  
  
    @Then("the response will return status {int} and id {int} and salary {int} and name {string} and age {int} and message {string}")
    public void verifyStatus(int statusCode, int id, int emp_Salary, String emp_name, int emp_age, String message ){
         
        validatableResponse.assertThat().statusCode(statusCode);
         
        validatableResponse.assertThat().body("data.id",equalTo(id));
         
        validatableResponse.assertThat().body("data.employee_salary",equalTo(emp_Salary));
         
        validatableResponse.assertThat().body("data.employee_name",equalTo(emp_name));
         
        validatableResponse.assertThat().body("data.employee_age",equalTo(emp_age));
         
        validatableResponse.assertThat().body("message",equalTo(message));      
         
    }
}

In order to use REST assured effectively, it’s recommended to statically import methods from the following classes:

import io.restassured.RestAssured.*
import io.restassured.matcher.RestAssuredMatchers.*
import static org.hamcrest.Matchers.*

given() method is imported from package:

import static io.restassured.RestAssured.given;

equalTo() method is imported from package:

import static org.hamcrest.Matchers;

Step 10 – Create a Cucumber Runner class 

A runner will help us to run the feature file and act as an interlink between the feature file and step definition Class. To know more about Runner, refer to this link.

import org.junit.runner.RunWith;

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)

@CucumberOptions(plugin ="pretty",features= {"src/test/resources/features/API_GET.feature"}, glue= {"com.example.gradle.apidemo"})

public class CucumberRunnerTest {

}

Step 11 – Run the tests from JUnit

You can execute the test script by right-clicking on the Test Runner class -> Run As JUnit.

Step 12 – Run the tests from the Command Line

Run the following Gradle task from the directory path where build.gradle file is located. To know more about this report, refer here.

gradle cucumber

Below is the screenshot of the execution of Cucumber tests in Command-Line.

Step 13 – Cucumber Report Generation

To get Cucumber Test Reports, add cucumber.properties under src/test/resources and add the below instruction in the file.

cucumber.publish.enabled=true

Below is the image of the Cucumber Report.

We are done! Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!!

Parallel Testing in Cucumber with JUnit4

Last Updated On

HOME

In this tutorial, I will explain Parallel Testing using Cucumber with JUnit4.

Cucumber-JVM allows parallel execution across multiple threads since version 4.0.0. There are several options to incorporate this built-in feature in a Cucumber project. You can do so by using JUnit, TestNG, or CLI.

Cucumber can be executed in parallel using JUnit and Maven test execution plugins.

In JUnit, the feature files are run in parallel rather than in scenarios, which means all the scenarios in a feature file will be executed by the same thread. You can use either Maven Surefire or Failsafe plugin to execute the runner. In this tutorial, I’m using the Maven Surefire plugin.

Table of Contents

  1. Dependency List
  2. Detailed Step Description
    1. Create a Maven project
    2. Update the Properties section in Maven pom.xml
    3. Add Cucumber and JUnit dependencies to the project
    4. Add Surefire plugin configuration to the build section of the POM
    5. Create a feature folder in src/test/resources
    6. Create the Page Object Model classes of LoginPage and ForgotPasswordPage feature files
    7. Create the Hook Class and Dependency Injection class (TestSetUp) and BaseTest class
    8. Create a Test Runner to run the tests
    9. Cucumber Report Generation
    10. Execute the tests from the command line
    11. Difference between Parallel tests and Non-Parallel Tests

Dependency List:-

  1. Cucumber Java – 7.11.1
  2. Cucumber JUnit – 7.11.1
  3. Java 11
  4. JUnit– 4.13.2
  5. Maven – 3.8.1
  6. Selenium – 34.8.0
  7. Maven Surefire Plugin – 3.0.0-M7
  8. Maven Compiler Plugin – 3.10.1

Detailed Step Description

Step 1 – Create a Maven project

Create a Maven project in your favorite IDE using the cucumber-archetype or by adding Cucumber dependencies to the POM as detailed here and Junit dependencies here. To know more about How to set up a Cucumber Maven project with Eclipse, please refer to this tutorial – Cucumber Tutorial – How to setup Cucumber with Eclipse.

Below is the structure of the project.

Step 2 – Update the Properties section in Maven pom.xml

   <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <selenium.version>4.8.0</selenium.version>
        <cucumber.version>7.11.1</cucumber.version>
        <junit.version>4.13.2</junit.version>
        <webdrivermanager.version>5.3.2</webdrivermanager.version>
        <extent.version>5.0.9</extent.version>
        <extent.cucumber.adapter.version>1.10.1</extent.cucumber.adapter.version>
        <maven.compiler.plugin.version>3.10.1</maven.compiler.plugin.version>
        <maven.surefire.plugin.version>3.0.0-M7</maven.surefire.plugin.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

Step 3 – Add Cucumber and JUnit dependencies to the project

Add below mentioned Cucumber-Java and Cucumber-JUnit dependencies to the project.

  <dependencies>

        <!--Cucumber Dependencies -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- Selenium Dependency -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>

        <!-- JUnit4 Dependency -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>


        <!-- Dependency Injection-->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-picocontainer</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- WebDriver Manager Dependency -->
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>${webdrivermanager.version}</version>
        </dependency>

    </dependencies>
  

Step 4 – Add Surefire plugin configuration to the build section of the POM

 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${maven.surefire.plugin.version}</version>
   <configuration>
        <parallel>methods</parallel>
        <useUnlimitedThreads>true</useUnlimitedThreads>
        <testFailureIgnore>true</testFailureIgnore>
	</configuration>
</plugin> 

To set the thread count to a specific number instead of useUnlimitedThreads use the below setting.

<configuration>
    <parallel>methods</parallel>
    <threadCount>4</threadCount>
</configuration>

The thread count in the above setting is 4 threads per core.

The complete POM.xml is shown below:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>ParallelTests_Cucumber_JUnit4_Demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <selenium.version>4.8.0</selenium.version>
        <cucumber.version>7.11.1</cucumber.version>
        <junit.version>4.13.2</junit.version>
        <webdrivermanager.version>5.3.2</webdrivermanager.version>
        <extent.version>5.0.9</extent.version>
        <extent.cucumber.adapter.version>1.10.1</extent.cucumber.adapter.version>
        <maven.compiler.plugin.version>3.10.1</maven.compiler.plugin.version>
        <maven.surefire.plugin.version>3.0.0-M7</maven.surefire.plugin.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>

        <!--Cucumber Dependencies -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- Selenium Dependency -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>

        <!-- JUnit4 Dependency -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>


        <!-- Dependency Injection-->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-picocontainer</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- WebDriver Manager Dependency -->
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>${webdrivermanager.version}</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.plugin.version}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven.surefire.plugin.version}</version>
                <configuration>
                    <parallel>methods</parallel>
                    <useUnlimitedThreads>true</useUnlimitedThreads>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Step 5 – Create a feature folder in src/test/resources

Add 2 feature files – LoginPage.feature and ForgotPasswordPage.feature in the features folder present in src/test/resources.

LoginPage.feature

Feature: Login to HRM Application

  Background:
    Given User is on Home page

  @ValidCredentials
  Scenario: Login with valid credentials - Feature 1, Scenario - 1

    When User enters username as "Admin" and password as "admin123"
    Then User should be able to login successfully

  @InvalidCredentials
  Scenario Outline: Login with invalid credentials - Feature 1, Scenario - 2

    When User enters username as "<username>" and password as "<password>"
    Then User should be able to see error message "<errorMessage>"

    Examples:
      | username    | password   | errorMessage                      |
      | Admin       | admin12$$  | Invalid credentials               |
      | admin$$     | admin123   | Invalid credentials               |
      | abc123      | xyz$$      | Invalid credentials               |
      |             | xyz$$      | Invalid credentials               |

ForgotPasswordPage.feature

Feature: Forgot Password Page

  Background:
    Given User is on Home page

  @BackFunctionality
  Scenario: Validate the cancel functionality - Feature 2, Scenario - 1

    When User clicks on Forgot your password? link
    Then User should be able to navigate to Reset Password page
    And User clicks on Cancel button to go back to Login Page

  @ResetFunctionality
  Scenario: Validate the Reset Password functionality - Feature 2, Scenario - 2

    When User clicks on Forgot your password? link
    Then User should be able to navigate to Reset Password page
    And User clicks on Reset Password button and provide username as "abc1234"
    And Verify the message "Reset Password link sent successfully"

Step 6 – Create the Page Object Model classes of LoginPage and ForgotPasswordPage feature files

Page Object Model class contains all the locators and the actions performed on these locators for the particular class to improve the readability and maintainability of the code.

Below are the Page Object Model classes for these feature files.

LoginPage

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
 
public class LoginPage {
 
    public WebDriver driver;
     
    By userName = By.name("username");
    By passWord = By.name("password");
    By login = By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[3]/button");
    By errorMessage = By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/div/div[1]/div[1]/p");
    By forgotPasswordLink = By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[4]/p");
    By loginPageTitle = By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/h5");
     
    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }
     
    public String getErrorMessage() {
        return driver.findElement(errorMessage).getText();
    }
 
    public void login(String strUserName, String strPassword) {
 
        // Fill user name
        driver.findElement(userName).sendKeys(strUserName);
 
        // Fill password
        driver.findElement(passWord).sendKeys(strPassword);
 
        // Click Login button
        driver.findElement(login).click();
 
    }
 
    // Click on Forgot Password link
    public void clickOnForgotPasswordLink() {
        driver.findElement(forgotPasswordLink).click();
    }
 
    //Get Login Page Title
    public String getLoginPageTitle() {
        return driver.findElement(loginPageTitle).getText();
    }
}

HomePage

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
 
public class HomePage {
 
    public WebDriver driver;
 
    public HomePage(WebDriver driver) {
        this.driver = driver;
    }
 
    By homePageUserName = By.xpath("//*[@id='app']/div[1]/div[1]/header/div[1]/div[1]/span/h6");
 
    public String getHomePageText() {
        return driver.findElement(homePageUserName).getText();
    }
}

ForgotPasswordPage

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
 
public class ForgotPasswordPage {
 
    WebDriver driver;
 
     By forgotPasswordPageTitle = By.xpath("//*[@id='app']/div[1]/div[1]/div/form/h6");
     By cancelBtn = By.xpath("//*[@id='app']/div[1]/div[1]/div/form/div[2]/button[1]");
     By resetPasswordBtn = By.xpath("//*[@id='app']/div[1]/div[1]/div/form/div[2]/button[2]");
     By userName = By.name("username");
     By resetMessage = By.xpath("//*[@id='app']/div[1]/div[1]/div/h6");
 
    public ForgotPasswordPage(WebDriver driver) {
        this.driver = driver;
    }
 
    // Get the Title of ForgotPage
    public String getForgotPageText() {
        return driver.findElement(forgotPasswordPageTitle).getText();
    }
 
    // Click Cancel Button
    public void clickOnCancelBtn() {
         driver.findElement(cancelBtn).click();
    }
 
    // Click ResetPassword Button
    public void clickOnRestPasswordBtn() {
        driver.findElement(resetPasswordBtn).click();
    }
 
    // Type username in TextBox
    public void TypeOnUsernameTextBox(String username) {
        driver.findElement(userName).sendKeys(username);
    }
 
    // Get Message
    public String getRestMessage() {
        return driver.findElement(resetMessage).getText();
    }
}

PageObjectManager – This class creates the object of all the above-mentioned Page Object Model classes. This an optional class. If you want you can create the objects in StepDefinition class also.

public class PageObjectManager {
 
    public LoginPage loginPage;
    public HomePage homePage;
    public ForgotPasswordPage forgotPasswordPage;
    public WebDriver driver;
 
 
    public PageObjectManager(WebDriver driver)
    {
        this.driver = driver;
    }
 
    public LoginPage getLoginPage()
    {
 
        loginPage= new LoginPage(driver);
        return loginPage;
    }
 
    public HomePage getHomePage()
    {
        homePage = new HomePage(driver);
        return homePage;
    }
 
    public ForgotPasswordPage getForgotPasswordPage()
    {
        forgotPasswordPage = new ForgotPasswordPage(driver);
        return forgotPasswordPage;
    }
}

Step 7 – Create the Step Definition classes for both feature files or Glue Code

Below is the Step Definition for LoginPage.feature.

import org.example.pageObjects.HomePage;
import org.example.pageObjects.LoginPage;
import org.example.pageObjects.PageObjectManager;
import org.example.utils.TestSetUp;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.junit.Assert;

public class LoginPageDefinitions {

    TestSetUp setUp;
    public PageObjectManager pageObjectManager;
    public LoginPage loginPage;
    public HomePage homePage;


    public LoginPageDefinitions(TestSetUp setUp)  {
        this.setUp = setUp;
        this.loginPage = setUp.pageObjectManager.getLoginPage();
        this.homePage= setUp.pageObjectManager.getHomePage();
    }

    @Given("User is on Home page")
    public void loginTest()  {
        setUp.baseTest.WebDriverManager().get("https://opensource-demo.orangehrmlive.com/");

    }

    @When("User enters username as {string} and password as {string}")
    public void goToHomePage(String userName, String passWord) {

        // login to application
        loginPage.login(userName, passWord);

        // go the next page

    }

    @Then("User should be able to login successfully")
    public void verifyLogin() {

        // Verify home page
        Assert.assertTrue(homePage.getHomePageText().contains("Dashboard"));

    }

    @Then("User should be able to see error message {string}")
    public void verifyErrorMessage(String expectedErrorMessage) {

        // Verify home page
        Assert.assertEquals(loginPage.getErrorMessage(),expectedErrorMessage);

    }

}

Below is the Step Definition for ForgotPasswordPage.feature.

import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.example.pageObjects.ForgotPasswordPage;
import org.example.pageObjects.LoginPage;
import org.example.pageObjects.PageObjectManager;
import org.example.utils.TestSetUp;
import org.junit.Assert;


public class ForgotPasswordPageDefinitions {

    TestSetUp setUp;
    PageObjectManager pageObjectManager;
    public LoginPage loginPage;
    public  ForgotPasswordPage forgotPasswordPage;

    public ForgotPasswordPageDefinitions(TestSetUp setUp) {
        this.setUp = setUp;
        this.loginPage = setUp.pageObjectManager.getLoginPage();
        this.forgotPasswordPage = setUp.pageObjectManager.getForgotPasswordPage();
    }

    @When("User clicks on Forgot your password? link")
    public void forgotPasswordLink() {

        loginPage.clickOnForgotPasswordLink();

    }

    @Then("User should be able to navigate to Reset Password page")
    public void verifyForgotPasswordPage() {

        Assert.assertEquals(forgotPasswordPage.getForgotPageText(),"Reset Password");

    }

    @Then("User clicks on Cancel button to go back to Login Page")
    public void verifyCancelBtn() {

        forgotPasswordPage.clickOnCancelBtn();
        Assert.assertEquals(loginPage.getLoginPageTitle(),"Login");

    }

    @Then("User clicks on Reset Password button and provide username as {string}")
    public void verifyResetPasswordBtn(String username) {

        forgotPasswordPage.TypeOnUsernameTextBox(username);
        forgotPasswordPage.clickOnRestPasswordBtn();

    }

    @Then("Verify the message {string}")
    public void verifyMessage(String message) {

        //  ForgotPasswordPage forgotPasswordPage = setUp.pageObjectManager.getForgotPasswordPage();
        Assert.assertEquals(forgotPasswordPage.getRestMessage(),message);

    }
}

Step 8 – Create the Hook Class and Dependency Injection class (TestSetUp) and BaseTest class

Below is the code for the ApplicationHook Class.

import io.cucumber.java.After;
import io.cucumber.java.AfterStep;
import io.cucumber.java.Scenario;
import org.example.utils.TestSetUp;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;

public class ApplicationHooks {

 public TestSetUp setUp;

    public ApplicationHooks(TestSetUp setUp) {
        this.setUp = setUp;
    }

    @After
    public void tearDown( )  {
        setUp.baseTest.WebDriverManager().quit();
    }

    @AfterStep
    public void addScreenshot(Scenario scenario) {

        WebDriver driver =  setUp.baseTest.WebDriverManager();
        if(scenario.isFailed()) {
            final byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
            scenario.attach(screenshot, "image/png", "image");
        }

    }

}

Below is the code for the Dependency Injection class. In Cucumber, if we want to share the state between multiple-step definition files, we will need to use dependency injection (DI). 

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import pageObjects.PageObjectManager;
 
public class TestSetUp {
 
    public WebElement errorMessage;
    public WebElement homePageUserName;
    public PageObjectManager pageObjectManager;
    public BaseTest baseTest;
 
    public TestSetUp()  {
 
        baseTest = new BaseTest();
        pageObjectManager = new PageObjectManager(baseTest.WebDriverManager());
 
    }
}

BaseTest class is used to initialize the WebDriver.

import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import java.time.Duration;
 
public class BaseTest {
 
    public WebDriver driver;
    public final static int TIMEOUT = 10;
 
    public WebDriver WebDriverManager ()  {
 
    
        if (driver == null) {
         
            WebDriverManager.chromedriver().setup();
            driver = new ChromeDriver();
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(TIMEOUT));
            driver.manage().window().maximize();
            driver.get(url);
 
        }
        return driver;
    }
}

Step 9 – Create a Test Runner to run the tests

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(tags = "", features = "src/test/resources/features", glue = "org.example.definitions")

public class CucumberRunnerTests  {

}

Step 10 – Cucumber Report Generation

To get Cucumber Test Reports, add cucumber.properties in src/test/resources and add the below instruction in the file.

cucumber.publish.enabled=true

Step 11 – Execute the tests from the command line

mvn clean test

Below is the execution screen. There are two feature files.

When we invoke the test through Maven, the surefire plugin executes the Feature files parallelly. Here, LoginPage has 5 scenarios and ForgotPasswordPage has 2 scenarios. So, initially when the execution will start 1 scenario from both the tests will be executed parallelly and then again one test from each feature will execute. Later, we will be left with 4 scenarios in the LoginPage feature file, so the scenario will run sequentially of the LoginPage feature file.

All the tests of a particular feature file are executed together as feature files are run in parallel, not scenarios.

Step 12 – Difference between Parallel tests and Non-Parallel Tests

Parallel Tests

Below is the Cucumber Report generated for parallel tests.

When the tests are run as JUnit tests from CucumberRunnerTests, then the tests are executed sequentially.

We are done! Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!!

In the next tutorial, I explained Parallel Testing in Cucumber with TestNG.

Cucumber Report Service

HOME

Reports can be generated in Cucumber in 2 ways.

  1. Generate report using Cucumber Report Services
  2. Generate local reports using built-in reporter plugins – html, json, junit, pretty

In this tutorial, I’ll explain about Cucumber Report Services. To use this service, we need to add Cucumber-jvm of version 6.7.0 and above.

<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-jvm -->
<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-jvm</artifactId>
    <version>6.8.1</version>
    <type>pom</type>
</dependency>

Another important pre-requisite is addition of below instruction in cucumber.properties file

cucumber.publish.enabled=true

Cucumber.properties file should be present in src/test/resources.

Now, execute the Cucumber tests by using command — mvn clean test in command line.

Below image shows the Console message generated

If you follow the link, you’ll see your report rendered in glorious color. Below is the image of such Cucumber Report. This report provide the information about

  1. No Of Test Scenarios Executed
  2. Passed vs Failed percentage
  3. When the tests are executed like 8 minutes ago or 15 hours ago
  4. Time taken to execute the Test Suite
  5. Window Version
  6. Java Version
  7. Cucumber-jvm Version
  8. Name of the feature file executed
  9. Test Scenario step execution – passed, failed, pending

The report is self destructed in 24 hours. It is mentioned as a warning at the top of the report.

To save the report for future use, click on the link – Keep your future reports forever. It will open a dialog box as shown below. You can create a Report Collection and the reports present in Report Collection are not auto-deleted.

Login to GitHub. A new page as shown below will appear. Create a collection by providing the name in the Name box and click the Create new collection button.

This page contains a token as shown in the image in the below screen. Mention this environment variable with token in cucumber.properties file which is present within src/test/resources.

When the tests will be executed, now the report will be saved under Cucumber Gradle Reports Collection.

Below is the image of the report which is saved under Cucumber Gradle Reports collection in GitHub.

If you have not added cucumber.properties to your project, then will get a screen as shown below.

Delete Report

The report can be deleted by clicking on Delete button on the report. Once you have clicked on Delete Report button, a dialog box as shown below is displayed. Once you click on Delete button present in the dialog box, the report is deleted permanently.

That’s it! Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!!

Testing of SpringBoot REST Application using Serenity BDD and Rest Assured for DELETE Method to delete a Resource

HOME

In the previous tutorial, I explained about the Testing of SpringBoot PUT Method. In this tutorial, I will discuss about the Testing of DELETE method to delete a Resource in SpringBoot application.

To know how you can test a SpringBoot Application in BDD format using Serenity, refer the tutorial related to Serenity BDD with Cucumber for SpringBoot application.

The structure of project and various Java classes present in SpringBoot application and the dependencies need in POM.xml to test springboot framework are mentioned here.

Below is the code of Student Controller which exposes the services of DELETE method of Student resource.

@DeleteMapping("/students/{id}")

@DeleteMapping annotation maps HTTP DELETE requests onto specific handler methods. It is a composed annotation that acts as a shortcut for @RequestMapping(method=RequestMethod.DELETE) .

Code of StudentController.java for DELETE method is below

@DeleteMapping("/students/{id}")
	public void deleteStudent(@PathVariable long id) {
		studentRepository.deleteById(id);
	}

Here, we are deleting the student resource by Id.

Scenario 1- Below picture shows how we can execute a sucessful DELETE Request Method using Postman

Before Deleting a Student Resource with Id 1001

In the below image, it shows all the details of student with Id 1001 and with status code of 201 is returned.

Now, we delete a Student with Id 1001 and the status code returned is 200.

After deleting the resource, again send a request to get the details of student of id 1001 which returns 404 – Not Found status.

Above scenario can be tested in the below way.

Feature: Delete Student  Request

   @DeleteStudent
   Scenario: Send a valid Request to delete a student

    Given I send a request to the URL "/students/1001" to get the detail of user with Id 1001
    When I send a request to the URL "/students/1001" to delete user
    Then the response will return status of 200 
    And I resend the request to the URL "/students/1001" to get status of 404

Test Code to test above scenario (StepDefinition file)

@SpringBootTest(classes = SpringBoot2RestServiceApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class DeleteStudentsDefinition {

	private final static String BASE_URI = "http://localhost";

	@LocalServerPort
	private int port;

	private ValidatableResponse validatableResponse1, validatableResponse2, validatableResponse3;

	private void configureRestAssured() {

		RestAssured.baseURI = BASE_URI;
		RestAssured.port = port;
	}

	protected RequestSpecification getAnonymousRequest() throws NoSuchAlgorithmException {
		configureRestAssured();
		return given();
	}

	@Given("^I send a request to the URL \"([^\"]*)\" to get the detail of user with Id 1001$")
	public void getRequest(String endpoint) throws Throwable {

		validatableResponse1 = getAnonymousRequest().contentType(ContentType.JSON).body(toString()).when().get(endpoint)
				.then();
	}

	@When("^I send a request to the URL \"([^\"]*)\" to delete user$")
	public void iSendARequest(String endpoint) throws Throwable {

		validatableResponse2 = getAnonymousRequest().contentType(ContentType.JSON).when().delete(endpoint).then();
	}

	@Then("^the response will return status of (\\d+)$")
	public void extractResponseOfValidStudent(int status) throws NoSuchAlgorithmException {
		validatableResponse2.assertThat().statusCode(equalTo(status));
	}

	@And("^I resend the request to the URL \"([^\"]*)\" to get status of (\\d+)$")
	public void reverifyStudent(String endpoint, int status) throws NoSuchAlgorithmException {
		validatableResponse3 = getAnonymousRequest().contentType(ContentType.JSON).body(toString()).when().get(endpoint)
				.then();
		validatableResponse3.assertThat().statusCode(equalTo(status));

   }
}

We can test the negative scenario similarly. Send a request with invalid student id, then we will get 500 Internal Server Error.

The next tutorial explains about the Testing of SpringBoot Exception Handling.

Testing of SpringBoot REST Application using Serenity BDD and Rest Assured for PUT Method to update a Resource

HOME

In the previous tutorial, I explained about the Testing of SpringBoot POST Method. In this tutorial, I will discuss about the Testing of PUT method to update a Resource in SpringBoot application.

To know how you can test a SpringBoot Application in BDD format using Serenity, refer the tutorial related to Serenity BDD with Cucumber for SpringBoot application.

The structure of project and various Java classes present in SpringBoot application and the dependencies need in POM.xml to test springboot framework are mentioned here.

Below is the code of Student Controller which exposes the services of PUT method of Student resource.

@PutMapping("/students/{id}")

PutMapping – It is Annotation for mapping HTTP PUT requests onto specific handler methods.

RequestBody – It maps body of the web request to the method parameter.

Code of StudentController.java for PUT method is below

@PutMapping("/students/{id}")
	public ResponseEntity<Object> updateStudent(@Valid @RequestBody Student student, @PathVariable long id) {

		Optional<Student> studentOptional = studentRepository.findById(id);
		if (!studentOptional.isPresent())
			return ResponseEntity.notFound().build();
		student.setId(id);
		studentRepository.save(student);
		return ResponseEntity.noContent().build();
	}

Here, we are checking if the student exists or not before updating the student. If the student does not exist, we return a not found status. Otherwise, we save the student details using studentRepository.save(student) method.

To test a PUT method of springboot application, you should use below code snippet to send a PUT request

Map<String, String> map = new HashMap<>();
   map.put("name", newName);
   map.put("passport", passport);

JSONObject newStudent = new JSONObject(map);
validatableResponse1 = getAnonymousRequest().contentType(ContentType.JSON).body(newStudent.toString()).when()
.put(endpoint).then();

Scenario 1- Below picture shows how we can execute a sucessful PUT Request Method using Postman

Before Updation

In the below image, details of all students with status code of 201 is returned.

Update Student – Here, I’m updating Student with Id 1001 from name Tom to Update and passport from AA234567 to AB000001

This is the image of updated Student of Id 1001

Above scenario can be tested in the below way.

Feature: Update Student Detail

   @UpdateStudent
   Scenario: Send a valid Request to update a student

    Given I send a request to the URL "/students" to get the detail of all users
    When I send a request to the URL "/students/1001" to update a user with name "Update" and passport as "AB000001"
    Then the response will return status of 204 for update student
    And I send a request to the URL "/students/1001" to get detail of updated student name as "Update"

Test Code to test above scenario (StepDefinition file)

@SpringBootTest(classes = SpringBoot2RestServiceApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class UpdateStudentDefinitions {

	private final static String BASE_URI = "http://localhost";

	@LocalServerPort
	private int port;

	private ValidatableResponse validatableResponse, validatableResponse1, validatableResponse2;

	private void configureRestAssured() {

		RestAssured.baseURI = BASE_URI;
		RestAssured.port = port;
		
	}

	protected RequestSpecification getAnonymousRequest() throws NoSuchAlgorithmException {
		configureRestAssured();
		return given();
	}

	@Given("^I send a request to the URL \"([^\"]*)\" to get the detail of all users$")
	public void getRequest(String endpoint) throws Throwable {

		validatableResponse = getAnonymousRequest().contentType(ContentType.JSON).body(toString()).when().get(endpoint)
				.then();
	}

	@When("^I send a request to the URL \"([^\"]*)\" to update a user with name \"([^\"]*)\" and passport as \"([^\"]*)\"$")
	public void updateRequest(String endpoint, String newName, String passport) throws Throwable {

		Map<String, String> map = new HashMap<>();
		map.put("name", newName);
		map.put("passport", passport);

		JSONObject newStudent = new JSONObject(map);

		validatableResponse1 = getAnonymousRequest().contentType(ContentType.JSON).body(newStudent.toString()).when()
				.put(endpoint).then();
	}

	@Then("^the response will return status of (\\d+) for update student$")
	public void extractResponse(int status) {

		validatableResponse1.assertThat().statusCode(equalTo(status));
	}

	@And("^I send a request to the URL \"([^\"]*)\" to get detail of updated student name as \"([^\"]*)\"$")
	public void extractUpdatedResponse(String endpoint, String newName) throws NoSuchAlgorithmException {

		validatableResponse2 = getAnonymousRequest().contentType(ContentType.JSON).when().get(endpoint).then();
		validatableResponse2.assertThat().body("name", equalTo(newName));
	}

Scenario 2- Below picture shows how we can execute a unsucessful PUT Request Method using Postman

In the above image, I am trying to update the name of invalid student id 1000, so the response returns the status of 404.

This can be tested by using above step definition.

To know about all the dependencies we need to add to pom.xml to run the SpringBoot Test, refer the tutorial about Testing of SpringBoot REST Application using Serenity BDD and Rest Assured for GET Method.

The next tutorial explains about the Testing of DELETE method in SpringBoot Application.

Testing of SpringBoot Exception Handling

HOME

In the previous tutorial, I explained about the Testing of SpringBoot POST Method. In this tutorial, I will discuss the Testing of Exceptions in the SpringBoot application.

Response Statuses for Errors

The most commonly used error codes in SpringBoot Application are:-

  • 404 – RESOURCE NOT FOUND
  • 400 – BAD REQUEST
  • 401 – UNAUTHORIZED
  • 415 – UNSUPPORTED TYPE – Representation not supported for the resource
  • 500 – SERVER ERROR

Default Exception Handling with Spring Boot

Spring Boot provides a good default implementation for exception handling for RESTful Services.

This is the response when you try getting details of a non-existing student. You can see that the response status is 500 – Internal Server Error.

Customizing Exception Handling with Spring Boot

There are 2 most commonly used annotations used in Error Handling – @ExceptionHandler and @ControllerAdvice.

What is ExceptionHandler?

The @ExceptionHandler is an annotation used to handle the specific exceptions and send the custom responses to the client.

@ExceptionHandler(StudentNotFoundException.class)


What is ControllerAdvice?

A controller advice allows you to use exactly the same exception-handling techniques but apply them across the whole application, not just to an individual controller.

A combination of Spring and Spring Boot provides multiple options to customize responses for errors.

@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

You can specify the Response Status for a specific exception along with the definition of the Exception with the ‘@ResponseStatus’ annotation.

@ResponseStatus(HttpStatus.NOT_FOUND)

I have defined the StudentNotFoundExceptionclass. This Exception will be thrown by the controller when no resource i.e. Student to be returned in our case is found with HTTP Status of NOT_FOUND (404).

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class StudentNotFoundException extends RuntimeException {

	public StudentNotFoundException(String exception) {
		super(exception);
	}
}

Now the response will be 404 – Not Found

{
    "timestamp": "2021-02-22T15:56:59.494+00:00",
    "status": 404,
    "error": "Not Found",
    "trace": "StudentNotFoundException: id-100,
    "message": "id-100",
    "path": "/students/100"
}

Customizing Error Response Structure

The default error response provided by Spring Boot contains all the details that are typically needed.

However, you might want to create a framework-independent response structure for your organization. In that case, you can define a specific error response structure.

import java.util.Date;

public class ExceptionResponse {
	private Date timestamp;
	private String message;
	private String details;

	public ExceptionResponse(Date timestamp, String message, String details) {
		super();
		this.timestamp = timestamp;
		this.message = message;
		this.details = details;
	}

	public Date getTimestamp() {
		return timestamp;
	}

	public String getMessage() {
		return message;
	}

	public String getDetails() {
		return details;
	}
}

To use ExceptionResponse to return the error response, let’s define a ControllerAdvice as shown below.

@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

	@ExceptionHandler(StudentNotFoundException.class)
	public final ResponseEntity<Object> handleUserNotFoundException(StudentNotFoundException ex, WebRequest request) {
		ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
				request.getDescription(false));
		return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
	}

Scenario 1-  How to execute a Get Request Method with an Invalid URL using Postman

The above scenario can be tested in the below way.

Feature: Student Exception

   @InvalidURL
   Scenario: Send a valid Request to create a student

    Given I send a request to the invalid URL "/students/100" to get a student details
    Then the response will return status of 404 and message "id-100"

Test Code to test the above scenario (StepDefinition file)

@SpringBootTest(classes = SpringBootRestServiceApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class CustomizedErrorsDefinition {

	private final static String BASE_URI = "http://localhost";

	@LocalServerPort
	private int port;

	private ValidatableResponse validatableResponse;

	private void configureRestAssured() {

		RestAssured.baseURI = BASE_URI;
		RestAssured.port = port;
	}

	protected RequestSpecification getAnonymousRequest() throws NoSuchAlgorithmException {
		configureRestAssured();
		return given();
	}

	@Given("^I send a request to the invalid URL \"([^\"]*)\" to get a student details$")
	public void iSendARequest(String endpoint) throws Throwable {

		validatableResponse = getAnonymousRequest().contentType(ContentType.JSON).when().get(endpoint).then();
	}

	@Then("^the response will return status of (\\d+) and message \"([^\"]*)\"$")
	public void extractResponse(int status, String message) {		validatableResponse.assertThat().statusCode(equalTo(status)).body("message", equalTo(message));
	}
}

To know more about SpringBootTest, refer to the tutorial on Integration Testing of SpringBoot.

@SpringBootTest(classes = SpringBootRestServiceApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)

To know about all the dependencies we need to add to pom.xml to run the SpringBoot Test, refer to the tutorial about Testing of SpringBoot REST Application using Serenity BDD and Rest Assured for GET Method.

To know how you can test a SpringBoot Application in BDD format using Serenity, refer to the tutorial related to Serenity BDD with Cucumber for SpringBoot application.

The next tutorial explains the Testing of SpringBoot Validation for RESTful Services.

Testing of SpringBoot Validation for RESTful Services

HOME

In the previous tutorial, I have explain about SpringBoot and how to perform Integration testing of SpringBoot Exception Handling. In this tutorial, I will explain about the Integration testing of Testing of SpringBoot Validation for RESTful Services.

What is Validation?

It is necessary that the response recieve from RestFul service should return meaningful information, like certain data type, constraints. If a response returns error message, then we expect it to provide information like clear error message, which field has an error, what is the type of error, proper status code and most importantly should not provide any sensitive information.

From SpringBoot 2.3, we also need to explicitly add the spring-boot-starter-validation dependency:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-validation</artifactId> 
</dependency>

To know about all the dependencies neede to test Springboot, please click here.

Below are various Java classes present in a SpringBoot REST Application/API

SpringBootRestServiceApplication.java – The Spring Boot Application class generated with Spring Initializer. This class acts as the launching point for application.

pom.xml – Contains all the dependencies needed to build this project. 

Student.java – This is JPA Entity for Student class

StudentRepository.java – This is JPA Repository for Student. This is created using Spring Data JpaRepository.

StudentController.java – Spring Rest Controller exposing all services on the student resource.

CustomizedExceptionHandler.java – This implements global exception handling and customize the responses based on the exception type.

ErrorDetails.java – Response Bean to use when exceptions are thrown from API.

StudentNotFoundException.java – Exception thrown from resources when student is not found.

data.sql –  Data is loaded from data.sql into Student table. Spring Boot would execute this script after the tables are created from the entities.

REST request validation annotations

ANNOTATIONUSAGE
@AssertFalseThe annotated element must be false.
@AssertTrueThe annotated element must be true.
@DecimalMaxThe annotated element must be a number whose value must be lower or equal to the specified maximum.
@DecimalMinThe annotated element must be a number whose value must be higher or equal to the specified minimum.
@FutureThe annotated element must be an instant, date or time in the future.
@MaxThe annotated element must be a number whose value must be lower or equal to the specified maximum.
@MinThe annotated element must be a number whose value must be higher or equal to the specified minimum.
@NegativeThe annotated element must be a strictly negative number.
@NotBlankThe annotated element must not be null and must contain at least one non-whitespace character.
@NotEmptyThe annotated element must not be null nor empty.
@NotNullThe annotated element must not be null.
@NullThe annotated element must be null.
@PatternThe annotated CharSequence must match the specified regular expression.
@PositiveThe annotated element must be a strictly positive number.
@SizeThe annotated element size must be between the specified boundaries (included).

Implementing Validation on the Resource

Add @Valid in addition to @RequestBody.

public ResponseEntity<Object> createStudent(@Valid @RequestBody Student student) {

Below is the example of Student.java class. Here, you can see I have used various validations.

Here, Student class is annotated with @Entity, indicating that it is a JPA entity. There is no @Table annotation so entity is mapped to a table named Student .)

Id property is annotated with @Id and also annotated with @GeneratedValue  to indicate that the ID should be generated automatically.

@Entity
public class Student {
    @Id
    @GeneratedValue
    private Long id;
 
    @NotNull
    @Size(min = 4, message = "Name should have atleast 4 characters")
    private String name;
 
   @NotBlank(message = "passportNumber is mandatory") 
   private String passportNumber;
 
    public Student() {
        super();
    }
 
    public Student(Long id, String name, String passportNumber) {
        super();
        this.id = id;
        this.name = name;
        this.passportNumber = passportNumber;
    }
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getPassportNumber() {
        return passportNumber;
    }
 
    public void setPassportNumber(String passportNumber) {
        this.passportNumber = passportNumber;
    }
}

We need a @ControllerAdvice to handle validation errors. Below is the snippet of validation error for invalid method arguement.

@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "Validation Failed",
				ex.getBindingResult().toString());
		return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST);
	}
}

Scenario 1 – Below picture shows how we can execute a POST Request where Name is passed as null using Postman

@NotNull
    private String name;

In the below image, I am trying to create a new Student with name as null.

Above scenario can be tested in the below way.

Feature: Validation of  Student Request

  @CreateStudentWithNoName
    Scenario: Send a Request to create a student with no name

    Given I send a request to the URL "/students" to create a user with name as null and passport as "RA000002"
    Then the response will return error message as "Validation Failed" and details contain error detail as "default message [name]]; default message [must not be null]"

Test Code to test above scenario (StepDefinition file)

@SpringBootTest(classes = SpringBootRestServiceApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class ValidationDefinitions {

	private final static String BASE_URI = "http://localhost";

	@LocalServerPort
	private int port;

	private ValidatableResponse validatableResponse, validatableResponse1;

	private void configureRestAssured() {

		RestAssured.baseURI = BASE_URI;
		RestAssured.port = port;
	}

	protected RequestSpecification getAnonymousRequest() throws NoSuchAlgorithmException {
		configureRestAssured();
		return given();
	}

	@Given("^I send a request to the URL \"([^\"]*)\" to create a user with name as null and passport as \"([^\"]*)\"$")
	public void iSendARequestwithNullName(String endpoint, String passport) throws Throwable {

		Map<String, String> map = new HashMap<>();
		map.put("name", null);
		map.put("passport", passport);

		JSONObject newStudent = new JSONObject(map);

		validatableResponse = getAnonymousRequest().contentType(ContentType.JSON).body(newStudent.toString()).when()
				.post(endpoint).then();
	}

	@Then("^the response will return error message as \"([^\"]*)\" and details contain error detail as \"([^\"]*)\"$")

	public void extractErrorResponse(String message, String details) {

		validatableResponse.assertThat().body("message", equalTo(message)).and().body(containsString(details));
	}
}

To know how to start the springBoot test and web environment. Please refer this link.

Scenario 2 – Below picture shows how we can execute a POST Request to create a student with Name as length outside the range using Postman

@Size(min = 4, max = 10, message = "Name should have atleast 4 characters and not more than 10 characters")
	private String name;

This means that name should have minimum 4 characters and maximum 10 character. Name not in this range will throw validation error with message as “Name should have atleast 4 characters and not more than 10 characters”

Above scenario can be tested in the below way.

 @CreateStudentWithInvalidName
   
    Scenario: Send a Request to create a student with invalid name

    Given I send a request to the URL "/students" to create a user with name as "SamuelBobin" and passport as "RA000003"
    Then the response will return error message as "Validation Failed" and details contain error detail as "Name should have atleast 4 characters and not more than 10 characters"

Test Code to test above scenario (StepDefinition file)

@Given("^I send a request to the URL \"([^\"]*)\" to create a user with name as \"([^\"]*)\" and passport as \"([^\"]*)\"$")
	public void iSendARequest(String endpoint, String newName, String passport) throws Throwable {

		Map<String, String> map = new HashMap<>();
		map.put("name", newName);
		map.put("passport", passport);

		JSONObject newStudent = new JSONObject(map);

		validatableResponse = getAnonymousRequest().contentType(ContentType.JSON).body(newStudent.toString()).when()
				.post(endpoint).then();
	}

	@Then("^the response will return error message as \"([^\"]*)\" and details contain error detail as \"([^\"]*)\"$")

	public void extractErrorResponse(String message, String details) {

		validatableResponse.assertThat().body("message", equalTo(message)).and().body(containsString(details));

	}

Scenario 3 – Below picture shows how we can execute a POST Request where creating a student with passport as not Blank using Postman

@NotBlank(message = "passportNumber is mandatory")
	private String passportNumber;

@NotBlank is to specify the attribute should not be blank and also a message when a validation error occurs “passportNumber is mandatory”.

This scenario can be tested as shown above.

Testing of SpringBoot REST Application using Serenity BDD and Rest Assured for POST Method to create a Resource

HOME

In the previous tutorial, I explained about the Testing of SpringBoot GET Method. In this tutorial, I will discuss about the Testing of POST method which create Resource in SpringBoot application.

First, let us see what are RequestBody and PathMapping annotations which are commonly used assnotations for POST Method

1. What is RequestBody

The @RequestBody annotation maps body of the web request to the method parameter. The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request.

public ResponseEntity<Object> createStudent(@Valid @RequestBody Student student) {

2. What is PathMapping

The @PathMapping annotation is the specialized version of the @RequestMapping annotation which acts as a shortcut for @RequestMapping(method=RequestMethod=POST).

Here, we will use @PostMapping. It is annotation for mapping HTTP POST requests onto specific handler.

@PostMapping is a composed annotation that acts as a shortcut for @RequestMapping(method=RequestMethod.POST)

@PostMapping("/students")

The dependencies need in POM.xml to test springboot framework is mentioned here.

Code of StudentController.java is below

@PostMapping("/students")
	public ResponseEntity<Object> createStudent(@Valid @RequestBody Student student) {
		Student savedStudent = studentRepository.save(student);

		URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
				.buildAndExpand(savedStudent.getId()).toUri();

		return ResponseEntity.created(location).build();
}

ResponseEntity.created(location).build(): Return a status of created. Also return the location of created resource as a Response Header.

Code of Student.java is below

@Entity
public class Student {
	@Id
	@GeneratedValue
	private Long id;

	@NotNull
	@Size(min = 4, message = "Name should have atleast 4 characters")
	private String name;

	private String passportNumber;

	public Student() {
		super();
	}

	public Student(Long id, String name, String passportNumber) {
		super();
		this.id = id;
		this.name = name;
		this.passportNumber = passportNumber;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPassportNumber() {
		return passportNumber;
	}

	public void setPassportNumber(String passportNumber) {
		this.passportNumber = passportNumber;
	}
}

This is the Student entity. Each entity must have at least two annotations defined: @Entity and @Id. The @Entity annotation specifies that the class is an entity and is mapped to a database table. The  @Table annotation specifies the name of the database table to be used for mapping. The @Id annotation specifies the primary key of an entity and the @GeneratedValue provides for the specification of generation strategies for the values of primary keys.

Here, Student class is annotated with @Entity, indicating that it is a JPA entity. (Because no @Table annotation exists, it is assumed that this entity is mapped to a table named Student .)

The Student  object’s id property is annotated with @Id so that JPA recognizes it as the object’s ID. The id property is also annotated with @GeneratedValue  to indicate that the ID should be generated automatically.

Scenario 1- Below picture shows how we can execute a sucessful POST Request Method on a Resource using Postman

In the below image, a new Student with name “Mat” is created and the status code returned is 201.

In the below image, I’m checking if the new user is created with name “Matt”. As id is auto genertaed, so the ID generation starts from 1.

Above scenario can be tested in the below way.

Feature: Create Student Request

   @CreateStudent
   Scenario: Send a valid Request to create a student

     Given I send a request to the URL "/students" to create a user with name "Matt" and passport as "RA000001"
    Then the response will return status of 201 for new student
    And I send a request to the URL "/students/1" to get detail of new student name as "Matt"

Test Code to test above scenario (StepDefinition file)

@SpringBootTest(classes = SpringBoot2RestServiceApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class CreateStudentDefinition {

	private final static String BASE_URI = "http://localhost";

	@LocalServerPort
	private int port;

	private ValidatableResponse validatableResponse, validatableResponse1;

	private void configureRestAssured() {

		RestAssured.baseURI = BASE_URI;
		RestAssured.port = port;	
	}

	protected RequestSpecification getAnonymousRequest() throws NoSuchAlgorithmException {
		configureRestAssured();
		return given();
	}

	@Given("^I send a request to the URL \"([^\"]*)\" to create a user with name \"([^\"]*)\" and passport as \"([^\"]*)\"$")
	public void iSendARequest(String endpoint, String newName, String passport) throws Throwable {

		JSONObject newStudent = new JSONObject();

		newStudent.put("name", newName);
		newStudent.put("passport", passport);

		validatableResponse = getAnonymousRequest().contentType(ContentType.JSON).body(newStudent.toString()).when()
				.post(endpoint).then();
	}

	@Then("^the response will return status of (\\d+) for new student$")
	public void extractResponseOfNewStudent(int status) {

		validatableResponse.assertThat().statusCode(equalTo(status));
	}

	@And("^I send a request to the URL \"([^\"]*)\" to get detail of new student name as \"([^\"]*)\"$")
	public void extractResponseOfNewStudent(String endpoint, String newName) throws NoSuchAlgorithmException {

		validatableResponse1 = getAnonymousRequest().contentType(ContentType.JSON).when().get(endpoint).then();	
		validatableResponse1.assertThat().body("name", equalTo(newName));

	}

Here, I have used JSONObject from org.json to create the JSON directly. I use the put() method and supply the key and value as an argument.

JSONObject newStudent = new JSONObject();

		newStudent.put("name", newName);
		newStudent.put("passport", passport);

There is another way to create a JSON using Map. Here we construct a custom Map and then pass it as an argument to JSONObject‘s constructor.

Map<String, String> map = new HashMap<>();
		map.put("name", newName);
		map.put("passport", passport);

		JSONObject newStudent  = new JSONObject(map);

For assertion purpose, we use Hamcrest Matchers. Hamcrest is a framework for software tests. Hamcrest allows checking for conditions in your code via existing matchers classes. It also allows you to define your custom matcher implementations.

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;

The next tutorial explains about the Testing of PUT method in SpringBoot Application.

Testing of SpringBoot REST Application using Serenity BDD and Rest Assured for GET Method

 HOME

In the previous tutorial, I have explain about SpringBoot and how to perform Integration testing of SpringBoot Application in BDD format using Serenity BDD and Cucumber. In this tutorial, I will explain about the Integration testing of SpringBoot Application for GET method.

Spring Boot is an open-source micro framework which provides Java developers with a platform to get started with an auto configurable production-grade Spring application. 

In this tutorial, lets see a SpringBoot  REST Application and how it can be tested with the help of Rest Assured and Serenity

Below is the structure of a SpringBoot  application project

Below are various Java classes present in a SpringBoot REST Application/API

  • SpringBootRestServiceApplication.java – The Spring Boot Application class generated with Spring Initializer. This class acts as the launching point for application.
  • pom.xml – Contains all the dependencies needed to build this project. 
  • Student.java – This is JPA Entity for Student class
  • StudentRepository.java – This is JPA Repository for Student. This is created using Spring Data JpaRepository.
  • StudentController.java – Spring Rest Controller exposing all services on the student resource.
  • CustomizedExceptionHandler.java – This implements global exception handling and customize the responses based on the exception type.
  • ErrorDetails.java – Response Bean to use when exceptions are thrown from API.
  • StudentNotFoundException.java – Exception thrown from resources when student is not found.
  • data.sql –  Data is loaded from data.sql into Student table. Spring Boot would execute this script after the tables are created from the entities.

HTTP also defines standard response codes.

  • 200 – SUCESS
  • 404 – RESOURCE NOT FOUND
  • 400 – BAD REQUEST
  • 201 – CREATED
  • 401 – UNAUTHORIZED
  • 415 – UNSUPPORTED TYPE – Representation not supported for the resource
  • 500 – SERVER ERROR 

Let’s consider a few HTTP Methods:

  • GET : Should not update anything. Should return same result in multiple calls.

Possible Return Codes 200 (OK) + 404 (NOT FOUND) +400 (BAD REQUEST) 

  • POST : Should create a new resource. Ideally return JSON with link to newly created resource. Same return codes as get possible. In addition – Return code 201 (CREATED) can be used.
  • PUT : Update a known resource. ex: update client details. Possible Return Codes : 200(OK) + 404 (NOT FOUND) +400 (BAD REQUEST) 
  • DELETE : Used to delete a resource. Possible Return Codes : 200(OK).

We will create a Student Resource exposing three services using proper URIs and HTTP methods:

  • Retrieve all Students – @GetMapping(“/students”)
  • Get details of specific student – @GetMapping(“/students/{id}”)
  • Delete a student – @DeleteMapping(“/students/{id}”)
  • Create a new student – @PostMapping(“/students”)
  • Update student details – @PutMapping(“/students/{id}”)

To Test a SpringBoot Application for GET Method, we are using Rest Assured with Serenity BDD. Below mentioned dependencies are added in POM.xml

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-test</artifactId>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>net.serenity-bdd</groupId>
   <artifactId>serenity-core</artifactId>
   <version>2.2.0version>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>net.serenity-bdd</groupId>
   <artifactId>serenity-spring</artifactId>
   <version>2.2.0</version>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>net.serenity-bdd</groupId>
   <artifactId>serenity-cucumber5</artifactId>
   <version>2.2.0</version>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>net.serenity-bdd</groupId>
   <artifactId>serenity-rest-assured</artifactId>
   <version>2.2.0</version>
</dependency>
     
<dependency>
   <groupId>net.serenity-bdd</groupId>
  <artifactId>serenity-screenplay-rest</artifactId>
   <version>2.2.0</version>
   <scope>test</scope>
</dependency>

First, let us see what are @RestController, @AutoWired, @GetMapping and @PathVariable annotations.

1. @RestController

SpringBoot RestController annotation is use to create RESTful web services using Spring MVC. Spring RestController takes care of mapping request data to the request defined handles method.

2. @Autowired

The Spring framework enables automatic dependency injection. In other words, by declaring all the bean dependencies in a Spring configuration file, Spring container can autowire relationships between collaborating beans. This is called Spring bean autowiring. To know more about Autowired, you can refer this tutorial.

3. @GetMapping

It is annotation for mapping HTTP GET requests onto specific handler methods.
Specifically, @GetMapping is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod.GET)

4. @PathVariable

This annotation can be used to handle template variables in the request URI mapping, and use them as method parameters.
In this example, we use @PathVariable annotation to extract the templated part of the URI represented by the variable {id}.

A simple GET request to /students/{id} will invoke retrieveStudent with the extracted id value

http://localhost:8080/students/1001

Code of StudentController.java is below

@RestController
public class StudentController {

     @Autowired
     private StudentRepository studentRepository;

     @GetMapping("/students")
     public List retrieveAllStudents() {
           return studentRepository.findAll();
     }

     @GetMapping("/students/{id}")
     public EntityModel retrieveStudent(@PathVariable long id) {
           Optional student = studentRepository.findById(id);
           if (!student.isPresent())
                throw new StudentNotFoundException("id-" + id);
           EntityModel resource = EntityModel.of(student.get());

           WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllStudents());
           resource.add(linkTo.withRel("all-students"));
           return resource;
     }

Scenario 1- Below picture shows how we can execute a Get Request Method on a Resource using Postman

Above scenario can be tested in the below way.

@ReceiveUserDetails
 Scenario Outline: Send a valid Request to get user details
  Given I send a request to the URL "/students" to get user details
  Then the response will return status 200 and id and names and passport_no 
    
Examples:
    |studentID    |studentNames  |studentPassportNo|
    |10010        |Tom           |A1234567         |
    |10020        |Shawn         |B1234568         |
    |10030        |John          |C1239875         |

Test Code to test above scenario (StepDefinition file)

@SpringBootTest(classes = com.springboot.rest.demo.SpringBootRestServiceApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)

public class GetStudentsDefinition {
 
     private final static String BASE_URI = "http://localhost";
     
     @LocalServerPort
     private int port;
     
     private ValidatableResponse validatableResponse;
 
     private void configureRestAssured() {
           RestAssured.baseURI = BASE_URI;
           RestAssured.port = port;
     }
 
     protected RequestSpecification getAnonymousRequest() throws NoSuchAlgorithmException {
           configureRestAssured();
           return given();
     }
 
     @Given("^I send a request to the URL \"([^\"]*)\" to get user details$")
     public void iSendARequest(String endpoint) throws Throwable {
           validatableResponse = getAnonymousRequest().contentType(ContentType.JSON)
    .when().get(endpoint).then();       
     }
 
     @Then("^the response will return status (\\d+) and id (.*) and names (.*) and passport_no (.*)$")
     public void extractResponse(int status, String id, String studentName, 
     String passportNo) {
     validatableResponse.assertThat().statusCode(equalTo(status)).body(containsString(id))
                     .body(containsString(studentName)).body(containsString(passportNo));
 
     }

1. The @SpringBootTest annotation tells Spring Boot to look for a main configuration class (one with @SpringBootApplication, for instance) and use that to start a Spring application context.

2. WebEnvironment.RANDOM_PORT is used to create run the application at some random server port.

3. For assertion purpose, we use Hamcrest Matchers. Hamcrest is a framework for software tests. Hamcrest allows checking for conditions in your code via existing matchers classes. It also allows you to define your custom matcher implementations.

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;

4. @LocalServerPort gets the reference of port where the server has started. It helps in building the actual request URIs to mimic real client interactions.

5. ContentType.JSON is imported fromrestassured as well as ValidatableResponse

Scenario 2- Below picture shows how we can execute a Get Request Method to get detail of a specific Student. Here, we want details of student of Id 10020

@ReceiveAUserDetail
   Scenario: Send a valid Request to get user details
    Given I send a request to the URL "/students/10020" to get user detail of a specific user
    Then the response will return status 200 and name "Shawn"

Test Code to test above scenario (StepDefinition file)

@Given("^I send a request to the URL \"([^\"]*)\" to get user detail of a specific user$")
     public void sendRequestForSpecificUser(String endpoint) throws Throwable {
           validatableResponse = getAnonymousRequest().contentType(ContentType.JSON).when().get(endpoint).then();
           System.out.println("RESPONSE :" + validatableResponse.extract().asString());
     }
 
     @Then("^the response will return status (\\d+) and name \"([^\"]*)\"$")
     public void extractResponseOfSpecificUser(int status, String name) {
          validatableResponse.assertThat().statusCode(equalTo(status)).body("name", equalTo(name));
     }

Scenario 3- Below picture shows how we can execute a Get Request Method for incorrect Student

@IncorrectUserId
   Scenario: Send a valid Request to get user details
    Given I send a request to the URL "/students/7" to get user detail of a specific user
    Then the response will return status 404 and message "id-7"

Test Code to test above scenario (StepDefinition file)

@Given("^I send a request to the URL \"([^\"]*)\" to get user detail of a specific user$")
     public void sendRequestForSpecificUser(String endpoint) throws Throwable {
           validatableResponse = getAnonymousRequest().contentType(ContentType.JSON).when().get(endpoint).then();
           System.out.println("RESPONSE :" + validatableResponse.extract().asString()); 
     }
 
     @Then("^the response will return status (\\d+) and message \"([^\"]*)\"$")
     public void extractResponseOfInvalidUser(int status, String message) {
         validatableResponse.assertThat().statusCode(equalTo(status)).body("message", equalTo(message));
 
     }

The next tutorial explains about the Testing of POST method in SpringBoot Application.