This tutorial describes how to create and run a Spring application in IntelliJ IDEA. For this purpose, we need IntelliJ IDEA Ultimate Version. The Ultimate edition is commercial version (which has trial version for 30 days post which you needs license).
IntelliJ Ultimate will create a Spring Boot Maven project generated by Spring Initializr. This is the quickest way to create a Spring application, and IntelliJ IDEA provides a dedicated project wizard for it.
Steps to create a new Spring Boot project
Step 1 – From the main menu, select File -> New -> Project.
Step 2 – In the left pane of the New Project wizard, select Spring Initializr.
From the Project SDK list, select the JDK that you want to use in the project. I have used – 11 Amazon Corretto version 11.0.10.
If the JDK is installed on your computer, but not defined in the IDE, select Add JDK and specify the path to the JDK home directory.
If you don’t have the necessary JDK on your computer, please Download JDK from here.
To check if you have Java installed on your machine or not, please use the below command in command prompt.
java -version
This shows that Java 11 is already installed on my machine.
Step 4 – Mention the Group and Artifact name. Other Information is auto populated. Change them, if you want something different than already mentioned. Click the Nextbutton.
Step 5 – Select the Spring Web dependency under Web and click the Next button. I have used this combination because I want to create a RESTful application using Apache Tomcat.
Step 6 – A new window appears where mention the location where you want to save the new project. I have created a folder springbootdemo and save the project files in that folder.
Step 7 – Below is the structure of new project on local machine
Step 8 – This is how the project looks in IntelliJ.
Spring Initializr generates a valid project structure with the following files:
A build configuration file – pom.xml for Maven.
A class with the main() method to bootstrap the application – SpringbootdemoApplication.
An empty JUnit test class – SpringbootdemoApplicationTests.
An empty Spring application configuration file – application.properties.
Step 9 – To run the application, Right click on the SpringbootdemoApplication.java class and select Run SpringbootdemoApplication
The springbootdemoApplication is started and this is the image of the execution screen.
REST Assured is a Java DSL for simplifying the testing of REST-based services built on top of HTTP Builder. It supports POST, GET, PUT, DELETE, OPTIONS, PATCH, and HEAD requests and can be used to validate and verify the response to these requests.
The 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.
Dependency List:
Springboot – 3.2.3
Java 17
JUnit – 4.13.2
Maven – 3.9.6
RestAssured – 5.3.2
Junit Vintage
Sample SpringBoot Application
Below is the sample SpringBoot application used for the testing.
The Spring Boot Application class is generated with Spring Initializer. This class acts as the launching point for the application.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
The JPA Entity is any Java POJO, which can represent the underlying table structure. As our service is based on the Student table, we will create a Student Entity object.
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@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;
}
}
The Repository represents the DAO layer, which typically does all the database operations. Thanks to Spring Data, who provides the implementations for these methods. Let’s have a look at our StudentRepository, which extends the JpaRepository. There are no method declarations here in the StudentRepository. That is because Spring Data’s JpaRepository has already declared basic CRUD methods.
Spring Rest Controller exposes all services on the student resource. RestController used for the below example is shown below.
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
@RestController
public class StudentController {
@Autowired
private StudentRepository studentRepository;
@GetMapping("/students")
public List<Student> retrieveAllStudents() {
return studentRepository.findAll();
}
@GetMapping("/students/{id}")
public EntityModel<Student> retrieveStudent(@PathVariable long id) {
Optional<Student> student = studentRepository.findById(id);
if (!student.isPresent())
throw new StudentNotFoundException("id-" + id);
EntityModel<Student> resource = EntityModel.of(student.get());
WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllStudents());
resource.add(linkTo.withRel("all-students"));
return resource;
}
@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();
}
@DeleteMapping("/students/{id}")
public void deleteStudent(@PathVariable long id) {
studentRepository.deleteById(id);
}
@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();
}
}
StudentNotFoundException
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);
}
}
application.properties
spring.jpa.defer-datasource-initialization=true
data.sql
insert into student values(10001,'Annie', 'E1234567');
insert into student values(20001,'John', 'A1234568');
insert into student values(30001,'David','C1232268');
insert into student values(40001,'Amy','D213458');
Implementation Steps
Step 1 – Add SpringbootTest and Rest-Assured dependencies to the project
Step 2 – Create a test file under src/test/java and write the test code
package org.example;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.ValidatableResponse;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringbootDemoTests {
private final static String BASE_URI = "http://localhost";
@LocalServerPort
private int port;
private ValidatableResponse validatableResponse;
private ValidatableResponse validatableResponse1;
@Before
public void configureRestAssured() {
RestAssured.baseURI = BASE_URI;
RestAssured.port = port;
}
/* Get operation - Get the details of a Student */
@Test
public void listUsers() {
validatableResponse = given()
.contentType(ContentType.JSON)
.when()
.get("/students")
.then()
.assertThat().statusCode(200);
}
/* Get operation - Get the details of a Student */
@Test
public void listAUser() {
validatableResponse = given()
.contentType(ContentType.JSON)
.when()
.get("/students/30001")
.then()
.assertThat().log().all().statusCode(200)
.body("id",equalTo(30001))
.body("name",equalTo("David"))
.body("passportNumber",equalTo("C1232268"));;
}
/* Create operation - Create a new Student */
@Test
public void createAUser() throws JSONException {
JSONObject newStudent = new JSONObject();
newStudent.put("name", "Timmy");
newStudent.put("passportNumber", "ZZZ12345");
validatableResponse = given()
.contentType(ContentType.JSON).body(newStudent.toString())
.when()
.post("/students")
.then()
.log().all().assertThat().statusCode(201);
/* Verify that a new Student is created */
validatableResponse1 = given()
.contentType(ContentType.JSON)
.when()
.get("/students/1")
.then()
.log().all().assertThat().statusCode(200)
.body("id",equalTo(1))
.body("name",equalTo("Timmy"))
.body("passportNumber",equalTo("ZZZ12345"));
}
/* Update operation - Update PassportNumber of a Student */
@Test
public void updateAUser() throws JSONException {
JSONObject newStudent = new JSONObject();
newStudent.put("name", "John");
newStudent.put("passportNumber", "YYYY1234");
validatableResponse = given()
.contentType(ContentType.JSON).body(newStudent.toString())
.when()
.put("/students/20001")
.then()
.log().all().assertThat().statusCode(204);
/* Verify that the updated Student has updated PassportNumber */
validatableResponse1 = given()
.contentType(ContentType.JSON)
.when()
.get("/students/20001")
.then()
.log().all().assertThat().statusCode(200)
.body("id",equalTo(20001))
.body("name",equalTo("John"))
.body("passportNumber",equalTo("YYYY1234"));
}
/* Delete operation - Delete a Student */
@Test
public void deleteAUser() throws JSONException {
validatableResponse = given()
.contentType(ContentType.JSON)
.when()
.delete("/students/10003")
.then()
.log().all().assertThat().statusCode(200);
/* Verify that the deleted Student Request returns STATUS 404 */
validatableResponse1 = given()
.contentType(ContentType.JSON)
.when()
.get("/students/10003")
.then()
.log().all().assertThat().statusCode(404);
}
}
When a class is annotated with @RunWith or extends a class annotated with @RunWith, JUnit will invoke the class it references to run the tests in that class instead of the runner built into JUnit.
SpringRunner is an alias for the SpringJUnit4ClassRunner. Here, we have simply annotated a JUnit 4-based test class with @RunWith(SpringRunner.class). The Spring TestContext Framework provides generic, annotation-driven unit and integration testing support that is agnostic of the testing framework in use (JUnit, TestNG).
We build the test class with @SpringBootTestannotation which starts up an Application Contextused throughout our test. In the classes property of @SpringBootTest annotation, we can specify which configuration classes build our Application Context. By default, @SpringBootTest annotation does not provide any web environment. In order to set up a test web server we need to use @SpringBootTest’s webEnvironment annotation. There are a few modes in which the web server can be started.
RANDOM_PORT – this is a recommended option where a real, embedded web server starts on a random port
DEFINED_PORT – web server will start on an 8080 or a port defined in application.properties
MOCK – loads a mock web environment where embedded servers are not started up.
Step 3 – Run the tests from JUnit
Right-click Run as JUnit Tests (Eclipse)
Right Click and select Run SpringBootDemoTests (IntelliJ)
Step 4 – Run the tests from Command Line
Open a command prompt and use the below command to run the tests.
mvn clean test
To run the tests from the command line, we need to add junit-vintage-engine dependency. Starting with Spring Boot 2.4, JUnit 5’s vintage engine has been removed from the spring-boot-starter-test.
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.
Now, let me explain this in simple language. Autowired provide us the object initialization.
Suppose, I have a Controller class called as UserController and a helper class called UserDaoService where I can write all the methods needed for UserController.
Now, I’ll annotate the helper class UserDaoService as @Component. I have created 3 methods in the helper class UserDaoService which are findAll(), save() and getOne().
Using findAll(), I will get data of all the users and in save() I will add the user in the list and findOne() will find a particular user from the list.
Objects in Spring Container is called Beans. When a class is annotated as @Component, it will create an object (bean) for that class. Here, in our example when we have annotated UserDaoService as @component , so at the time of application is in run mode it will create a bean for UserDaoService class in spring container.
In our controller class (UserController) there is dependency for Helper class (UserDaoService), so how our controller class beans know that there is helper class bean present in container. We need to told controller class bean to find out helper class bean and use it, so here @Autowired comes in picture. It will start to find bean for helper class and inject it into that variable so your variable in Initialized and ready to use.
Controller Class (UserResource)
@RestController
public class UserController
{
@Autowired
private UserDaoService service;
@GetMapping("/users")
public List<User> retriveAllUsers()
{
return service.findAll();
}
}
Helper Class (UserDaoService)
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.stereotype.Component;
@Component
public class UserDaoService
{
public static int usersCount=5;
//creating an instance of ArrayList
private static List<User> users=new ArrayList<>();
//static block
static
{
//adding users to the list
users.add(new User(1, "John", new Date()));
users.add(new User(2, "Tom", new Date()));
users.add(new User(3, "Perry", new Date()));
}
//method that retrieve all users from the list
public List<User> findAll()
{
return users;
}
//method that add the user in the list
public User save(User user)
{
if(user.getId()==null)
{
//increments the user id
user.setId(++usersCount);
}
users.add(user);
return user;
}
//method that find a particular user from the list
public User findOne(int id)
{
for(User user:users)
{
if(user.getId()==id)
return user;
}
return null;
}
}
In the above example, UserDaoService is Autowired in UserController class using by property. Using Autowired, I have called method findAll().
@Autowired
private UserDaoService service;
Use of @Autowired in Integration Testing of SpringBoot Application
Lets imagine a condition where different tests need to have a common test step. Suppose I have created 2 feature files where 1 feature file contain the tests related to valid Request/Response and another feature file create test scenarios related to various service errors. In both the cases, a test step where we have to send an access token to the requests is common. If I create this step in both StepDefinition files, it is duplicacy of code. To avoid this situation, we can create a Helper Class name as CommonStepDefinitions and declare it as @Componenet and @Autowired this helper class to specific StepDefinition class.
In the below example, I have created an access Token and I need to pass this access Token to the SpringBoot request to proceed further. I have @Autowired the CommonDefinition class to ValidRequestDefinitions and get the value of access token here and pass it to the request.
CommonStepDefinitions.class
@Component
@SpringBootTest
public class CommonStepDefinitions {
@Given("^I generate an auth token to pass bearer token to request$")
public String generateToken() throws JSONException {
validatableResponse = given().auth().basic(username, password).param("grant_type", grant_type).when()
.post(authUrl).then();
JSONObject token = new JSONObject(validatableResponse.extract().asString());
accessToken = token.get("access_token").toString();
return accessToken;
}
Main StepDefinition.class (ValidRequestDefinitions)
@SpringBootTest
public class ValidRequestDefinitions{
private ValidatableResponse validatableResponse;
private String bearerToken ;
//Autowired on Property
@Autowired
CommonStepDefinitions helper;
@When("^I send a valid request to the URL (.*)$")
public void sendRequestToGenerateProcess(String endpoint) {
bearerToken = helper.generateToken();
validatableResponse = given().header("Authorization", "Bearer " + bearerToken).contentType(ContentType.JSON)
.body(toString()).when().get(endpoint).then();
}
That’s it! Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!!
WireMock is a library for stubbing and mocking web services. It constructs a HTTP server that act as an actual web service. When a WireMock server is in action, we have the option to set up expectations, call the service, and then verify the behavior of service. It allows us to:-
Capture the incoming HTTP requests and write assertions for the captured HTTP requests.
Configure the response returned by the HTTP API when it receives a specific request.
Identify the stubbed and/or captured HTTP requests by using request matching.
Configure request matchers by comparing the request URL, request method, request headers, cookies, and request body with the expected values.
Use WireMock as a library or run it as a standalone process.
Why do we need to Wiremock a SpringBoot Application
Suppose your SpringBoot Application calls an external application and this call to external service bear some cost. You can’t test the application without calling the external service, So in this case, we can use WireMock to mock the external application while testing the REST service that you are developing in SpringBoot.
Spring Cloud Contract WireMock
The Spring Cloud Contract WireMock modules allow us to use WireMock in a Spring Boot application.
To use WireMock in SpringBoot, add Maven dependency
We can use JUnit @Rules to start and stop the server. To do so, use the WireMockRule convenience class to obtain optionsinstance, as the following example shows:
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().port(8081));
Wiremock runs as a stub server, and you can register stub behavior by using a Java API or by using static JSON declarations as part of your test.
Let us take the example of Student SpringBoot Application. The Response JSON of Student is shown below.
Creating a first WireMock mock service
Let us create a simple wiremock service with return status code as 200 and statsus message as “OK”.
@SpringBootTest
public class Wiremock_Example {
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().port(8081));
@Test
public void test() {
stubFor(get(urlPathEqualTo("/students"))
.willReturn(aResponse().withHeader("Content-Type", "application/json")
.withStatus(200).withStatusMessage("OK")));
}
}
options is imported from package static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
@Rule & @Test are from junit
WireMockRule is from com.github.tomakehurst.wiremock.junit.WireMockRule
The following code will configure a response with a status of 200 and status message of “OK” to be returned when the relative URL exactly matches /students (including query parameters). The body of the response will be “Tom” and Content-Type header will be sent with a value of application/json.
To create the stub described above via the JSON API, the following document can either be posted tohttp://<host>:<port>/__admin/mappings or placed in a file with a .json extension under the mappings directory:
Dynamic port numbers
Wiremock has the facility to pick free HTTP and HTTPS ports in SpringBoot application, which is a good idea if we need to run the applications concurrently.
@ClassRule
public static WireMockClassRule wiremock = new WireMockClassRule(
WireMockSpring.options().dynamicPort());
Below is an example which shows how you can mock an applictaion on dynamic ports. In the below example, the localhost port is dynamic here (57099).
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class Wiremock_Example {
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().dynamicPort());
@Test
public void test() {
stubFor(get(urlPathEqualTo("/students")).willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(200).withStatusMessage("OK").withBody("Tom")));
}
}
How to run and test Mock Service?
In the below example, I will use Rest Assured tests to validate the behaviour of Mock Service. First, I need to get the mock service up and running and then use it in a test.
@SpringBootTest
public class Wiremock_Example {
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().port(8089));
public void setupMockService() {
wireMockRule.stubFor(get(urlPathEqualTo("/students"))
.willReturn(aResponse().withHeader("Content-Type", "application/json")
.withStatus(200).withStatusMessage("OK")));
}
@Test
public void positiveTest() {
setupMockService();
given()
.when()
.get("http://localhost:8089/students")
.then()
.assertThat().statusCode(200);
}
}
In the below example, it is shown that the response content of a wiremock service can also be verified.
@SpringBootTest
public class Wiremock_Example {
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().port(8090));
public void setupMockService() {
wireMockRule.stubFor(get(urlPathEqualTo("/students"))
.willReturn(aResponse().withHeader("Content-Type", "application/json")
.withStatus(200).withStatusMessage("OK").withBody("Tom")));
}
@Test
public void responseMessageTest() {
setupMockService();
given()
.when()
.get("http://localhost:8090/students")
.then()
.assertThat().body(containsString("Tom"));
}
}
How to read response body from a file?
To read the body content from a file, place the file under the __files directory under src/test/resources when running from the JUnit rule.To make your stub use the file, use withBodyFile() on the response builder with the file’s path relative to __files.
public class Wiremock_Example {
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().port(8085));
public void setupMockService() {
wireMockRule.stubFor(get(urlPathEqualTo("/students"))
.willReturn(aResponse().withHeader("Content-Type", "application/json") .withStatus(200).withStatusMessage("OK").withBodyFile("Response.json")));
}
@Test
public void responseFileTest() {
setupMockService();
given()
.when()
.get("http://localhost:8085/students").then()
.assertThat()
.body(containsString("John"));
}
}
To read more about SpringBoot Wiremock, you can refer SpringBoot Wiremock website.
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.
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.
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.
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 PUTrequests 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.
In the previous tutorial, I explained about theTesting of SpringBoot POST Method. In this tutorial, I will discuss the Testing of Exceptions in the SpringBoot application.
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);
}
}
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 ExceptionResponseto 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 below picture shows how we can 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));
}
}
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.
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
ANNOTATION
USAGE
@AssertFalse
The annotated element must be false.
@AssertTrue
The annotated element must be true.
@DecimalMax
The annotated element must be a number whose value must be lower or equal to the specified maximum.
@DecimalMin
The annotated element must be a number whose value must be higher or equal to the specified minimum.
@Future
The annotated element must be an instant, date or time in the future.
@Max
The annotated element must be a number whose value must be lower or equal to the specified maximum.
@Min
The annotated element must be a number whose value must be higher or equal to the specified minimum.
@Negative
The annotated element must be a strictly negative number.
@NotBlank
The annotated element must not be null and must contain at least one non-whitespace character.
@NotEmpty
The annotated element must not be null nor empty.
@NotNull
The annotated element must not be null.
@Null
The annotated element must be null.
@Pattern
The annotated CharSequence must match the specified regular expression.
@Positive
The annotated element must be a strictly positive number.
@Size
The 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”.
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 HttpMessageConverterto 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 POSTrequests 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 idproperty 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.
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.
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}”)
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.
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));
}