Spring Boot Testing Best Practices
Tags: spring, springboot, junit, mockito
Published: 2024-06-23 13:48:21

Testing is a crucial aspect of software development, ensuring that your application behaves as expected and is free from bugs. Spring Boot provides excellent support for testing, making it easier to write unit tests, integration tests, and test RESTful services. In this blog, we will explore best practices for testing Spring Boot applications, covering unit testing, integration testing, and using MockMvc for testing RESTful services

**Writing Unit Tests for Spring Boot Applications**
Unit tests are the foundation of a solid test suite. They focus on testing individual components in isolation, such as methods or classes, without depending on external systems like databases or web servers

**Best Practices for Unit Testing:**

**Use JUnit 5:** JUnit 5 is the latest version of the popular testing framework and provides powerful features for writing tests. Ensure you include the necessary dependencies in your pom.xml or build.gradle file

```

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.1</version>
    <scope>test</scope>
</dependency>

```

**Mock Dependencies:** Use mocking frameworks like Mockito to mock dependencies and focus on testing the logic of the class under test

```
@SpringBootTest
public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void testFindUserById() {
        User user = new User(1L, "John");    

when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        User result = userService.findUserById(1L);

        assertEquals("John", result.getName());
    }
}

```

**Test Boundary Conditions:** Ensure you test edge cases, null values, and invalid inputs to make your tests comprehensive


**Write Fast and Isolated Tests:** Unit tests should run quickly and independently. Avoid using external resources like databases or file systems


**Use Test Doubles for Dependencies:** When necessary, create stub or mock implementations for dependencies that your class under test interacts with. This helps isolate the component being tested


```
@Test
void testUserCreation() {
    User user = new User(null, "Jane");
    when(userRepository.save(any(User.class))).thenReturn(new User(1L, "Jane"));
    
    User createdUser = userService.createUser(user);
    
    assertNotNull(createdUser.getId());
    assertEquals("Jane", createdUser.getName());
}

```

**Integration Testing with Spring Boot :** 
Integration tests verify that different parts of the application work together as expected. They test the application’s behavior in a realistic environment, including interactions with databases, web servers, and other systems

**Best Practices for Integration Testing :** 

**Use @SpringBootTest:** The @SpringBootTest annotation loads the full application context and is useful for writing integration tests

```
@SpringBootTest
@ExtendWith(SpringExtension.class)
public class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @Test
    void testFindUserById() {
        User user = new User(1L, "John");
        userRepository.save(user);
        User result = userService.findUserById(1L);
        assertEquals("John", result.getName());
    }
}

```

**Use @Transactional for Database Tests:** Use the @Transactional annotation to ensure that database changes are rolled back after each test, maintaining a clean state


```
@SpringBootTest
@Transactional
public class UserServiceIntegrationTest {
    // Integration tests
}
```

**Profile-Specific Configuration:** Use different application properties for testing to avoid conflicts with development or production environments

```
# src/test/resources/application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: password

```

**Test Slices:** Use test slices like @WebMvcTest, @DataJpaTest, @RestClientTest to load only the necessary parts of the application context, making tests faster and more focused 


```
@DataJpaTest
public class UserRepositoryTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void testSaveUser() {
        User user = new User(null, "Alice");
        User savedUser = userRepository.save(user);
        assertNotNull(savedUser.getId());
        assertEquals("Alice", savedUser.getName());
    }
}

```

**Using MockMvc for Testing RESTful Services**
MockMvc is a powerful tool for testing Spring MVC controllers. It allows you to perform HTTP requests and assert responses without starting the entire web server

**Best Practices for Using MockMvc**

**Setup MockMvc with @WebMvcTest:** Use the @WebMvcTest annotation to test only the web layer and configure MockMvc

```
@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void testGetUserById() throws Exception {
        User user = new User(1L, "John");
        when(userService.findUserById(1L)).thenReturn(user);

        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("John"));
    }
}

```

**Test Different Scenarios:** Ensure you test various scenarios, including success, failure, and edge cases 

```
@Test
void testGetUserById_NotFound() throws Exception {
    when(userService.findUserById(1L)).thenThrow(new UserNotFoundException());

    mockMvc.perform(get("/users/1"))
           .andExpect(status().isNotFound());
}

```

 **Use JSONPath for Response Assertions:** Use JSONPath expressions to assert specific parts of the JSON response. 

```
mockMvc.perform(get("/users/1"))
       .andExpect(status().isOk())
       .andExpect(jsonPath("$.id").value(1))
       .andExpect(jsonPath("$.name").value("John"));

```

**Verify Interactions:** Use Mockito to verify that service methods are called as expected.

```
verify(userService).findUserById(1L);

```

**Test with Different HTTP Methods:** Test various HTTP methods (GET, POST, PUT, DELETE) to ensure your RESTful services handle them correctly 

```
@Test
void testCreateUser() throws Exception {
    User user = new User(null, "Jane");
    when(userService.createUser(any(User.class))).thenReturn(new User(1L, "Jane"));
    
    mockMvc.perform(post("/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"name\": \"Jane\"}"))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value(1))
            .andExpect(jsonPath("$.name").value("Jane"));
}

```

**Test Error Handling:** Ensure your tests cover error scenarios and that your application returns appropriate error responses

```
@Test
void testCreateUser_InvalidInput() throws Exception {
    mockMvc.perform(post("/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"name\": \"\"}"))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.errors").isNotEmpty());
}

```
**Conclusion**
Testing is a vital part of developing reliable and maintainable Spring Boot applications. By following these best practices for unit testing, integration testing, and using MockMvc, you can ensure that your application is robust and behaves as expected. Remember to write tests that are fast, isolated, and comprehensive to cover various scenarios and edge cases 

Happy testing!