---
title: "Testing Spring web applications"
canonical: "https://docs.getxray.app/space/XRAY/301477412/Testing%20Spring%20web%20applications"
format: markdown
---
> ℹ️ **What you'll learn**
> ℹ️ 
> ℹ️ - Define tests using Spring Boot
> ℹ️ - Run the test and push the test report to Xray
> ℹ️ - Validate in Jira that the test results are available

# Overview

[Spring Framework](https://spring.io/) is a well-known Java framework to build Java-based applications, supporting [IoC (Inversion of Control) principle](https://docs.spring.io/spring-framework/reference/core/beans/introduction.html).

[Sprint Boot](https://spring.io/projects/spring-boot) provides an opinionated extension on top of Spring that aims to minimize configuration burden and ease the implementation of applications.

With Spring it's possible to create web applications, REST services, and more.

# Prerequisites

  


<details>
<summary>Expand</summary>

For this example we will use the built-in testing facilities provided by Spring to test the application developed in Spring Boot

 We will need:

- Java and Maven[ ](https://www.cypress.io/)installed in your machine
- [xray-junit-extension](https://github.com/Xray-App/xray-junit-extensions) maven plugin, to take advantage of some annotations that allow us to embed additional information on the generated JUnit XML report (optional)
  - create a file to enable the generation of an enhanced JUnit XML report that Xray can take advantage of
    - **test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener**
  - configure the new reporter to generate the report in specific file (e.g., reports/TEST-junit-jupiter.xml)
    - **test/resources/xray-junit-extensions.properties**
</details>

  


To start using Spring Boot please follow the [Quick Start Guide](https://spring.io/quickstart) documentation; you can also use [Spring initializr](https://start.spring.io/) to make a working skeleton of a project using Spring and its dependencies.

Usually, Spring applications have these layers:

1. web/presentation layer
  1. controllers, exception handlers, filters, ...
2. service layer
  1. services with business logic
3. persistence/data layer
  1. JPA Repository, Entity
  2. database

  


The target SUT is a web application implemented using Spring Boot, having a REST API to manage users and some controllers that return text acting like typical servlets. 

Our Spring application provides:

- 3 controllers:
  - IndexController: that is used to return the text "Welcome to this amazing website!" whenever accessing the root page /
  - GreetingController: that is used to return a greeting message (e.g., "Hello, xxx!") based on an HTML template
  - UserRestController: that provides a REST API to manage users using several endpoints under the* /users* base URL
- 1 service:
  - UserService/UserServiceImpl: to perform business logic on users; in this case just as a small layer on top of the repository (UserRepository)
- 1 entity and 1 associated repository:
  - User: a persistable entity
  - UserRepository: a JPA repository of User objects

  


We can run our Spring application from the command line.

```shell
mvn spring-boot:run
```

> Macro (inline-media-image)

> Macro (inline-media-image)



> Macro (inline-media-image)

> Macro (inline-media-image)



  


We'll implement JUnit 5 tests for all these layers:

- web:
  - we'll test the IndexController and GreetingController controllers
  - we'll test the REST API provided by the UserRestController controller
- service:
  - we will unit test the UserServiceImpl, avoiding usage of database, to test the logic of the service; we'll use Mockito to mock responses of the UserRepository
- data:
  - we'll test the UserRepository in isolation, without loading the web environment
  - an in-memory database (H2) will be used

  


> ℹ️ <span style="color: #0e101a">Spring Boot supports </span><span style="color: #4a6ee0">[test slicing](https://spring.io/blog/2016/08/30/custom-test-slice-with-spring-boot-1-4)</span><span style="color: #0e101a">; the idea is to provide </span><span style="color: #4a6ee0">[slices](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.spring-boot-applications.autoconfigured-tests)</span><span style="color: #0e101a"> of the whole </span><span style="color: #0e101a">*ApplicationContext*</span><span style="color: #0e101a"> by loading fewer components, thus providing efficiency. We'll see more about @DataJpaTest and @WebMvcTest ahead.</span>

  


To test at the data layer, we can use [@DataJpaTest](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.html) as a test slice to test our UserRepository repository and the User entity.

- By default, tests annotated with @DataJpaTest are transactional and roll back at the end of each test. They also use an embedded in-memory database (H2).
- Can make use of [@TestEntityManage](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManager.html): a test-friendly EntityManager that provides additional methods useful for testing

  


To test at the service layer, we won't need to boot the application; we can perform unit tests and mock dependency on the data layer.

  


To test at web layer we can follow different approaches by annotating the related test classes: 

- [@SpringBootTest](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html)
  - loads the full application; more resource intensive
  - web server port is injected using @LocalServerPort; a friendly REST client can be used by an injected TestRestTemplate
  - can use a specific database for testing purposes using [@AutoConfigureTestDatabase](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabase.html) and [@TestPropertySource](https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-testpropertysource.html)
- [@SpringBootTest](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html)(webEnvironment = WebEnvironment.MOCK, classes = ...) + @AutoConfigureMockMvc
  - loads the full application except the web server itself
    - <span style="color: #000000">*Another useful approach is to not start the server at all but to test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost all of the full stack is used, and your code will be called in exactly the same way as if it were processing a real HTTP request but without the cost of starting the server*</span>
  - access to MVC framework is made using an injected reference to MockMvc using @Autowired
- [@WebMvcTest](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html)`(xxx.class)`
  - loads a test slice focused just on the web layer, providing a simplified web environment
  - access to MVC framework is made using an injected reference to MockMvc using @Autowired
  - <span style="color: #474747">usually</span><span style="color: #474747"> </span>`@WebMvcTest`<span style="color: #474747"> </span><span style="color: #474747">is used in combination with</span><span style="color: #474747"> </span>[@MockBean](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html)<span style="color: #474747"> </span><span style="color: #474747">or</span><span style="color: #474747"> </span>[@Import](https://docs.spring.io/spring-framework/docs/6.1.3/javadoc-api/org/springframework/context/annotation/Import.html)<span style="color: #474747"> </span><span style="color: #474747">to create any collaborators required by your</span><span style="color: #474747"> </span>`@Controller`<span style="color: #474747"> </span><span style="color: #474747">beans.</span>

  


Let's see some examples, precisely focused more on the web layer.

  


The following code snippet shows usage of @WebMvcTest to test a slice containing just the web layer. In this case we're testing the output of the root page.

Even though we don't have to use them, we'll also take advantage of 2 annotations provided by the xray-junit-extensions maven plugin to showcase additional features:

- **@XrayTest**: to map the Junit  test to an existing Test issue that already exists in Jira
- **@Requirement**: to link the test to an existing requirement/story in Jira

  


##### **IndexControllerMockedIT.java**

```java
package com.idera.xray.tutorials.springboot;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import com.idera.xray.tutorials.springboot.boundary.IndexController;

// @SpringBootTest
// @AutoConfigureMockMvc; it is implied whenever @WebMvcTest is used

// @WebMvcTest annotation is used to test only the web layer of the application
// It disables full auto-configuration and instead apply only configuration relevant to MVC tests
@WebMvcTest(IndexController.class)
public class IndexControllerMockedIT {

	@Autowired
	private MockMvc mvc;

	@Test
	@XrayTest(key = "XT-438")
	@Requirement("XT-437")
    public void getWelcomeMessage() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.TEXT_PLAIN))
				.andExpect(status().isOk())
				.andExpect(content().string(equalTo("Welcome to this amazing website!")));
	}
}
```

  


The following code snippet loads the whole application using @SpringBootTest to test the REST API endpoints used to manage users.

##### **UserRestControllerIT.java**

```java
package com.idera.xray.tutorials.springboot;

import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.boot.test.web.server.LocalServerPort;
import com.idera.xray.tutorials.springboot.data.User;
import com.idera.xray.tutorials.springboot.data.UserRepository;
import app.getxray.xray.junit.customjunitxml.annotations.XrayTest;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;

/* @SpringBootTest loads the full application, including the web server
 * @AutoConfigureTestDatabase is used to configure a test database instead of the application-defined database
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase
class UserRestControllerIT {

    @LocalServerPort
    int randomServerPort;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private UserRepository repository;

    User user1;

    @BeforeEach
    public void resetDb() {
        repository.deleteAll();
        user1 = repository.save(new User("Sergio Freire", "sergiofreire", "dummypassword"));
    }

    @Test
     void createUserWithSuccess() {
        User john = new User("John Doe", "johndoe", "dummypassword");
        ResponseEntity<User> entity = restTemplate.postForEntity("/api/users", john, User.class);

        List<User> foundUsers = repository.findAll();
        assertThat(foundUsers).extracting(User::getUsername).contains("johndoe");
    }

    @Test
     void dontCreateUserForInvalidData() {
        User john = new User("John Doe", "", "dummypassword");
        ResponseEntity<User> response = restTemplate.postForEntity("/api/users", john, User.class);
 
        // ideally, the server shouldnt return 500, but 400 (bad request)
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
 
        List<User> found = repository.findAll();
        assertThat(found).hasSize(1);
        assertThat(found).extracting(User::getName).doesNotContain("John Doe");
    }

    @Test
    void getUserWithSuccess() {
        String endpoint = UriComponentsBuilder.newInstance()
                .scheme("http")
                .host("127.0.0.1")
                .port(randomServerPort)
                .pathSegment("api", "users", user1.getId().toString() )
                .build()
                .toUriString();

        ResponseEntity<User> response = restTemplate.exchange(endpoint, HttpMethod.GET, null, new ParameterizedTypeReference<User>() {
        });
        User user = response.getBody();

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(user1.equals(user)).isTrue();
    }

    @Test
    void getUserUnsuccess() throws JSONException {
        /*
        String endpoint = UriComponentsBuilder.newInstance()
                .scheme("http")
                .host("127.0.0.1")
                .port(randomServerPort)
                .pathSegment("api", "users", "-1" )
                .build()
                .toUriString();

        */

        ResponseEntity<JSONObject> response = restTemplate.exchange("/api/user/-1", HttpMethod.GET, null, new ParameterizedTypeReference<JSONObject>() {
        });

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
    }
    

    @Test
     void listAllUsersWithSuccess()  {
        createTempUser("Amanda James", "amanda", "dummypassword");
        createTempUser("Robert Junior", "robert", "dummypassword");

        ResponseEntity<List<User>> response = restTemplate
                .exchange("/api/users", HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() {
                });

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).extracting(User::getName).containsExactly("Sergio Freire", "Amanda James", "Robert Junior");
    }

    @Test
    void deleteUserWithSuccess() {
        ResponseEntity<User> response = restTemplate.exchange("/api/users/" + user1.getId(), HttpMethod.DELETE, null, User.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().getName()).isEqualTo("Sergio Freire");

        List<User> found = repository.findAll();
        assertThat(found).isEmpty();
    }

    @Test
    void deleteUserUnsuccess() {
        ResponseEntity<User> response = restTemplate.exchange("/api/users/" + (user1.getId()+2), HttpMethod.DELETE, null, User.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);

        List<User> found = repository.findAll();
        assertThat(found).hasSize(1);
    }

    private void createTempUser(String name, String username, String password) {
        User user = new User(name, username, password);
        repository.saveAndFlush(user);
    }

}
```

  


In our case, we have tests that will be picked by *surefire *plugin and other ones that will be picked by *failsafe* plugin.

Once the code is implemented it can be executed with the following command:

```shell
mvn test failsafe:integration-test
```

  


The results are immediately available in the terminal.

![image](media://77f6c6db-9ca4-4b35-b4e2-df080ddcd2aa)

  


Ultimately this will lead to multiple JUnit XML reports (in `target/surefire-reports/` and `target/failsafe-reports/`, respectively).

If we use the xray-junit-extensions maven plugin, it will generate 1 JUnit XML report (i.e., in `reports/TEST-junit-jupiter.xml`) with all results of the last task executed (i.e., the integration tests ran by failsafe on the previous *mvn* command). 

  


 In this example, all tests have succeeded, as seen in the previous terminal screenshot. It generates the following JUnit XML report.

##### **JUnit XML Report**

```
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="JUnit Jupiter" tests="11" skipped="0" failures="0" errors="0" time="6"
    hostname="Sergios-MBP.lan" timestamp="2024-02-12T18:20:45">
    <properties>
        <property name="user.timezone" value="Europe/Lisbon" />
    </properties>
    <testcase name="getPersonalizedGreeting"
        classname="com.idera.xray.tutorials.springboot.GreetingControllerMockedIT" time="0"
        started-at="2024-02-12T18:20:43.713745" finished-at="2024-02-12T18:20:44.054213">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.GreetingControllerMockedIT]/[method:getPersonalizedGreeting()]
display-name: getPersonalizedGreeting()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="dontCreateUserForInvalidData"
        classname="com.idera.xray.tutorials.springboot.UserRestControllerIT" time="0"
        started-at="2024-02-12T18:20:44.497692" finished-at="2024-02-12T18:20:44.83319">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.UserRestControllerIT]/[method:dontCreateUserForInvalidData()]
display-name: dontCreateUserForInvalidData()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="getUserUnsuccess"
        classname="com.idera.xray.tutorials.springboot.UserRestControllerIT" time="0"
        started-at="2024-02-12T18:20:44.904108" finished-at="2024-02-12T18:20:44.924705">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.UserRestControllerIT]/[method:getUserUnsuccess()]
display-name: getUserUnsuccess()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="deleteUserWithSuccess"
        classname="com.idera.xray.tutorials.springboot.UserRestControllerIT" time="0"
        started-at="2024-02-12T18:20:44.867452" finished-at="2024-02-12T18:20:44.885325">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.UserRestControllerIT]/[method:deleteUserWithSuccess()]
display-name: deleteUserWithSuccess()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="createUserWithSuccess"
        classname="com.idera.xray.tutorials.springboot.UserRestControllerIT" time="0"
        started-at="2024-02-12T18:20:44.886039" finished-at="2024-02-12T18:20:44.903411">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.UserRestControllerIT]/[method:createUserWithSuccess()]
display-name: createUserWithSuccess()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="getDefaultGreeting"
        classname="com.idera.xray.tutorials.springboot.GreetingControllerMockedIT" time="0"
        started-at="2024-02-12T18:20:44.05502" finished-at="2024-02-12T18:20:44.06002">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.GreetingControllerMockedIT]/[method:getDefaultGreeting()]
display-name: getDefaultGreeting()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="getWelcomeMessage"
        classname="com.idera.xray.tutorials.springboot.IndexControllerMockedIT" time="0"
        started-at="2024-02-12T18:20:45.118582" finished-at="2024-02-12T18:20:45.123352">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.IndexControllerMockedIT]/[method:getWelcomeMessage()]
display-name: getWelcomeMessage()
]]></system-out>
        <properties>
            <property name="requirements" value="XT-437" />
            <property name="test_key" value="XT-438" />
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="getUserWithSuccess"
        classname="com.idera.xray.tutorials.springboot.UserRestControllerIT" time="0"
        started-at="2024-02-12T18:20:44.834228" finished-at="2024-02-12T18:20:44.86667">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.UserRestControllerIT]/[method:getUserWithSuccess()]
display-name: getUserWithSuccess()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="listAllUsersWithSuccess"
        classname="com.idera.xray.tutorials.springboot.UserRestControllerIT" time="0"
        started-at="2024-02-12T18:20:44.942625" finished-at="2024-02-12T18:20:44.961687">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.UserRestControllerIT]/[method:listAllUsersWithSuccess()]
display-name: listAllUsersWithSuccess()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="getWelcomeMessage"
        classname="com.idera.xray.tutorials.springboot.IndexControllerIT" time="0"
        started-at="2024-02-12T18:20:42.730085" finished-at="2024-02-12T18:20:43.45441">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.IndexControllerIT]/[method:getWelcomeMessage()]
display-name: getWelcomeMessage()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <testcase name="deleteUserUnsuccess"
        classname="com.idera.xray.tutorials.springboot.UserRestControllerIT" time="0"
        started-at="2024-02-12T18:20:44.925561" finished-at="2024-02-12T18:20:44.941928">
        <system-out><![CDATA[
unique-id: [engine:junit-jupiter]/[class:com.idera.xray.tutorials.springboot.UserRestControllerIT]/[method:deleteUserUnsuccess()]
display-name: deleteUserUnsuccess()
]]></system-out>
        <properties>
            <property name="_dummy_" value="" />
        </properties>
    </testcase>
    <system-out><![CDATA[
unique-id: [engine:junit-jupiter]
display-name: JUnit Jupiter
]]></system-out>
</testsuite>
```

  


  


---

# Integrating with Xray

<span style="color: #172B4D">Once we produced JUnit reports with the test results, it is a matter of importing those results into your Jira instance. This can be done by simply submitting automation results to Xray through the REST API, by using one of the available CI/CD plugins (e.g. for Jenkins), or using the Jira interface.</span>

  


> Macro (ui-tabs)
> 
> > Macro (legacy-content)
> 
> > Macro (legacy-content)

  


# Tips

- <span style="color: #000000">after results are imported, in Jira Tests can be linked to existing requirements/user stories, so you can track the impact on their coverage.</span>
- <span style="color: #000000">results from multiple builds can be linked to an existing Test Plan, to facilitate the analysis of test result trends across builds.</span>
- <span style="color: #000000">results can be associated with a Test Environment, in case you want to analyze coverage and test results by that environment later on. A Test Environment can be a testing stage (e.g. dev, staging, preprod, prod) or a identifier of the device/application used to interact with the system (e.g. browser, mobile OS).</span>

  


  


---

# References

- [Spring project](https://spring.io/)
- [Spring Framework](https://spring.io/projects/spring-framework)
- [Sprint Boot](https://spring.io/projects/spring-boot)
- [Spring guides](https://spring.io/guides)
- [Spring Testing](https://docs.spring.io/spring-framework/reference/testing/introduction.html#page-title)
- [article on Testing the web layer](https://spring.io/guides/gs/testing-web)

> Macro (toc)

> Macro (style)