SoFunction
Updated on 2025-03-08

Detailed explanation of Spring Boot unit testing and integration testing implementation

Learn how to use the tools provided in this tutorial and write unit tests and integration tests in a Spring Boot environment.

1. Overview

In this article, we will learn how to write unit tests and integrate them intoSpring BootIn the environment. You can find a lot of tutorials on this topic online, but it is difficult to find all the information you need in one page. I often notice that junior developers confuse the concepts of unit testing and integration testing, especially when it comes to the Spring ecosystem. I will try to clarify how different annotations are used in different contexts.

2. Unit Test vs. Integration Test

That's what Wikipedia saysUnit Testingof:

In computer programming, unit testing is a software testing method used to test a single unit of source code, a collection of one or more computer program modules, and related control data, usage processes and operational processes to determine whether they are suitable for use.

Integration Testing

“Integration testing (sometimes called integration and testing, abbreviated as I&T) is a phase of software testing in which individual software modules are combined to perform testing.”

In short, when we are doing unit testing, we just test a unit of code, testing only one method at a time, not all other components that interact with the component that is testing.

On the other hand, in integration testing, we test the integration between the components. Due to unit testing, we can tell that these components behave consistently with the needs, but it is not clear how they work together. This is the responsibility of integration testing.

3. Java unit test

All Java developers know that JUnit is the main framework for performing unit tests. It provides many annotations to assert expectations.

Hamcrest is an additional framework for software testing. Hamcrest allows the use of existing matcher classes to check conditions in the code, and also allows custom matcher implementations. To use Hamcrest matcher in JUnit, you must useassertThatStatement followed by one or more matchers.

Here you can see simple tests using both frameworks:

import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;

import ;

import ;
import ;

public class AssertTests {
 @Test
 public void testAssertArrayEquals() {
  byte[] expected = "trial".getBytes();
  byte[] actual = "trial".getBytes();
  assertArrayEquals("failure - byte arrays not same", expected, actual);
 }

 @Test
 public void testAssertEquals() {
  assertEquals("failure - strings are not equal", "text", "text");
 }

 @Test
 public void testAssertFalse() {
  assertFalse("failure - should be false", false);
 }

 @Test
 public void testAssertNotNull() {
  assertNotNull("should not be null", new Object());
 }

 @Test
 public void testAssertNotSame() {
  assertNotSame("should not be same Object", new Object(), new Object());
 }

 @Test
 public void testAssertNull() {
  assertNull("should be null", null);
 }

 @Test
 public void testAssertSame() {
  Integer aNumber = (768);
  assertSame("should be same", aNumber, aNumber);
 }

 // JUnit Matchers assertThat
 @Test
 public void testAssertThatBothContainsString() {
  assertThat("albumen", both(containsString("a")).and(containsString("b")));
 }

 @Test
 public void testAssertThatHasItems() {
  assertThat(("one", "two", "three"), hasItems("one", "three"));
 }

 @Test
 public void testAssertThatEveryItemContainsString() {
  assertThat((new String[] { "fun", "ban", "net" }), everyItem(containsString("n")));
 }

 // Core Hamcrest Matchers with assertThat
 @Test
 public void testAssertThatHamcrestCoreMatchers() {
  assertThat("good", allOf(equalTo("good"), startsWith("good")));
  assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
  assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
  assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
  assertThat(new Object(), not(sameInstance(new Object())));
 }

 @Test
 public void testAssertTrue() {
  assertTrue("failure - should be true", true);
 }
}

4. Introduction to our case

Let's write a simple program. The purpose is to provide a basic search engine for comics.

4.1. Maven dependencies

First, we need to add some dependencies to our project.

<dependency>
 <groupId></groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
</dependency>
<dependency>
 <groupId></groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
 <groupId></groupId>
 <artifactId>lombok</artifactId>
 <version>1.16.20</version>
 <scope>provided</scope>
</dependency>

4.2. Definition Model

Our model is very simple, consisting of only two classes: Manga and MangaResult

4.2.1. Manga Class

The Manga class represents the Manga instance retrieved by the system. Use Lombok to reduce boilerplate code.

package ;
import ;
import ;
import ;
import ;
import ;

@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class Manga {
  private String title;
  private String description;
  private Integer volumes;
  private Double score;
}

4.2.2. MangaResult

The MangaResult class is a wrapper class that contains a Manga List.

package ;
import ;
import ;
import ;
import ;
@Getter @Setter @NoArgsConstructor
public class MangaResult {
  private List<Manga> result;
}

4.3. Implement Service

To implement this Service, we will use the free API interface provided by Jikan Moe.

RestTemplate is a Spring class used to initiate REST calls to the API.

package ;

import ;

import org.;
import org.;
import ;
import ;
import ;

import ;
import ;

@Service
public class MangaService {

  Logger logger = ();
  private static final String MANGA_SEARCH_URL="/search/manga/";
  
  @Autowired
  RestTemplate restTemplate;
  
  public List<Manga> getMangasByTitle(String title) {
    return (MANGA_SEARCH_URL+title, ).getBody().getResult();
  }
}

4.4. Implement Controller

The next step is to write a REST Controller that exposes two endpoints, one synchronous and the other asynchronous, which is only used for testing purposes. The Controller uses the Service defined above.

package ;

import ;
import ;

import org.;
import org.;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

import ;
import ;

@RestController
@RequestMapping(value = "/manga")
public class MangaController {

  Logger logger = ();
  
  @Autowired
  private MangaService mangaService;  
  
  @RequestMapping(value = "/async/{title}", method = )
  @Async
  public CompletableFuture<List<Manga>> searchASync(@PathVariable(name = "title") String title) {
    return ((title));
  }
  
  @RequestMapping(value = "/sync/{title}", method = )
  public @ResponseBody <List<Manga>> searchSync(@PathVariable(name = "title") String title) {
    return (title);
  }
}

4.5. Start and test the system

mvn spring-boot:run

Then, Let's try it:

curl http://localhost:8080/manga/async/ken
curl http://localhost:8080/manga/sync/ken

Sample output:

{ 
  "title":"Rurouni Kenshin: Meiji Kenkaku Romantan",
  "description":"Ten years have passed since the end of Bakumatsu, an era of war that saw the uprising of citizens against the Tokugawa shogunate. The revolutionaries wanted to create a time of peace, and a thriving c...",
  "volumes":28,
  "score":8.69
},
{ 
  "title":"Sun-Ken Rock",
  "description":"The story revolves around Ken, a man from an upper-class family that was orphaned young due to his family's involvement with the Yakuza; he became a high school delinquent known for fighting. The only...",
  "volumes":25,
  "score":8.12
},
{ 
  "title":"Yumekui Kenbun",
  "description":"For those who suffer nightmares, help awaits at the Ginseikan Tea House, where patrons can order much more than just Darjeeling. Hiruko is a special kind of a private investigator. He's a dream eater....",
  "volumes":9,
  "score":7.97
}

5. Unit testing of Spring Boot applications

Spring Boot provides a powerful class to make testing simple:@SpringBootTestannotation

This annotation can be specified on a test class running based on Spring Boot.

In addition to the regular Spring TestContext Framework, it also provides the following features:

  • When @ContextConfiguration (loader=…) is not specifically declared, SpringBootContextLoader is used as the default ContextLoader.
  • Automatically search for @SpringBootConfiguration when the nested @Configuration annotation is not used and the relevant class is not explicitly specified.
  • Allows customization of Environment properties using Properties.
  • Provides support for different web environment modes, including the ability to start a fully running web server on defined or random ports.
  • Register the TestRestTemplate and/or WebTestClient Bean for use in web tests that run completely on the web server.

Here we only have two components to test: MangaService and MangaController

5.1. Perform unit testing of MangaService

To test MangaService, we need to isolate it from external components. In this example, only one external component is needed: RestTemplate, which we use to call the remote API.

What we need to do is simulate the RestTemplate Bean and have it always respond with a fixed given response. Spring Test combines and extends the Mockito library. Through the @MockBean annotation, we can configure mock beans.

package ;
import static ;
import static ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .;
import ;
import static ;

import ;
import ;
import ;
import ;

@RunWith()
@SpringBootTest
public class MangaServiceUnitTest {
  
  @Autowired
  private MangaService mangaService;
  
  // MockBean is the annotation provided by Spring that wraps mockito one
  // Annotation that can be used to add mocks to a Spring ApplicationContext.
  // If any existing single bean of the same type defined in the context will be replaced by the mock, if no existing bean is defined a new one will be added.
  @MockBean
  private RestTemplate template;
  
  @Test
  public void testGetMangasByTitle() throws IOException {
    // Parsing mock file
    MangaResult mRs = JsonUtils.jsonFile2Object("", );
    // Mocking remote service
    when((any(), any())).thenReturn(new ResponseEntity(mRs, ));
    // I search for goku but system will use mocked response containing only ken, so I can check that mock is used.
    List<Manga> mangasByTitle = ("goku");
    assertThat(mangasByTitle).isNotNull()
      .isNotEmpty()
      .allMatch(p -> ()
        .toLowerCase()
        .contains("ken"));
  }
  
}

5.2. Unit test of MangaController

As done in unit tests for MangaService, we need to isolate components. In this case, we need to simulate the MangaService Bean.

Then we have another problem... The Controller part is part of the system that manages HttpRequest, so we need a system to simulate this behavior rather than starting a full HTTP server.

MockMvc is the Spring class that performs this operation. It can be set up in different ways:

  • Using Standalone Context
  • Using WebApplication Context
  • Let Spring load all contexts by using @SpringBootTest and @AutoConfigureMockMvc on the test class to achieve automatic assembly
  • Let Spring load the web layer context by using the @WebMvcTest annotation on the test class for automatic assembly
package ;

import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;

import ;
import ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .;
import ;
import ;
import ;

import ;
import ;
import ;

@SpringBootTest
@RunWith()
public class MangaControllerUnitTest {

  MockMvc mockMvc;
  
  @Autowired
  protected WebApplicationContext wac;
  
  @Autowired
  MangaController mangaController;
  
  @MockBean
  MangaService mangaService;
  
  /**
   * List of samples mangas
   */
  private List<Manga> mangas;
  
  @Before
  public void setup() throws Exception {
     = standaloneSetup().build();// Standalone context
    // mockMvc = (wac)
    // .build();
    Manga manga1 = ()
      .title("Hokuto no ken")
      .description("The year is 199X. The Earth has been devastated by nuclear war...")
      .build();
    Manga manga2 = ()
      .title("Yumekui Kenbun")
      .description("For those who suffer nightmares, help awaits at the Ginseikan Tea House, where patrons can order much more than just Darjeeling. Hiruko is a special kind of a private investigator. He's a dream eater....")
      .build();
    mangas = new ArrayList<>();
    (manga1);
    (manga2);
  }
  
  @Test
  public void testSearchSync() throws Exception {
    // Mocking service
    when((any())).thenReturn(mangas);
    (get("/manga/sync/ken").contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$[0].title", is("Hokuto no ken")))
      .andExpect(jsonPath("$[1].title", is("Yumekui Kenbun")));
  }

  @Test
  public void testSearchASync() throws Exception {
    // Mocking service
    when((any())).thenReturn(mangas);
    MvcResult result = (get("/manga/async/ken").contentType(MediaType.APPLICATION_JSON))
      .andDo(print())
      .andExpect(request().asyncStarted())
      .andDo(print())
      // .andExpect(status().is2xxSuccessful()).andReturn();
      .andReturn();
    // ().getAsyncContext().setTimeout(10000);
    (asyncDispatch(result))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(jsonPath("$[0].title", is("Hokuto no ken")));
  }
}

As you can see in the code, the first solution is chosen because it is the lightest one, and we can have better governance of objects loaded in the Spring context.

In asynchronous testing, you must first simulate the asynchronous behavior by calling the service and then starting the asyncDispatch method.

6. Integration testing of Spring Boot applications

For integration testing, we want to provide downstream communication to check our main components.

6.1. Integration test of MangaService

This test is also very simple. We don't need to mock anything, because our purpose is to call the remote Manga API.

package ;

import static ;

import ;

import ;
import ;
import ;
import ;
import .;

import ;
import ;

@RunWith()
@SpringBootTest
public class MangaServiceIntegrationTest {

  @Autowired
  private MangaService mangaService;
  
  @Test
  public void testGetMangasByTitle() {
      List<Manga> mangasByTitle = ("ken");
      assertThat(mangasByTitle).isNotNull().isNotEmpty();
  }
}

6.2. Integration test of MangaController

This test is very similar to unit tests, but in this case we no longer need to mock MangaService.

package ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;
import static ;

import ;
import ;
import ;
import ;
import ;
import ;
import .;
import ;
import ;
import ;

import ;

@SpringBootTest
@RunWith()
public class MangaControllerIntegrationTest {

  // @Autowired
  MockMvc mockMvc;
  
  @Autowired
  protected WebApplicationContext wac;
  
  @Autowired
  MangaController mangaController;
  
  @Before
  public void setup() throws Exception {
     = standaloneSetup().build();// Standalone context
    // mockMvc = (wac)
    // .build();
  }
  
  @Test
  public void testSearchSync() throws Exception {
    (get("/manga/sync/ken").contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.*.title", hasItem(is("Hokuto no Ken"))));
  }
  
  @Test
  public void testSearchASync() throws Exception {
    MvcResult result = (get("/manga/async/ken").contentType(MediaType.APPLICATION_JSON))
      .andDo(print())
      .andExpect(request().asyncStarted())
      .andDo(print())
      .andReturn();
    (asyncDispatch(result))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.*.title", hasItem(is("Hokuto no Ken"))));
  }
}

7. Conclusion

We have learned about the main differences between unit testing and integration testing in Spring Boot environments, and we have learned about frameworks that simplify testing writing like Hamcrest. Of course, it can also be in mineGitHub repositoryAll codes are found in.

original:/articles/unit-and-integration-tests-in-spring-boot-2

Author: Marco Giglione

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.