DEV Community

Abhishek Singh
Abhishek Singh

Posted on

Spring Boot Testing Best Practices

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>

Enter fullscreen mode Exit fullscreen mode

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());
    }
}

Enter fullscreen mode Exit fullscreen mode

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());
}

Enter fullscreen mode Exit fullscreen mode

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());
    }
}

Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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());
    }
}

Enter fullscreen mode Exit fullscreen mode

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"));
    }
}

Enter fullscreen mode Exit fullscreen mode

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());
}

Enter fullscreen mode Exit fullscreen mode

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"));

Enter fullscreen mode Exit fullscreen mode

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

verify(userService).findUserById(1L);

Enter fullscreen mode Exit fullscreen mode

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"));
}

Enter fullscreen mode Exit fullscreen mode

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());
}

Enter fullscreen mode Exit fullscreen mode

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!

Top comments (1)

Collapse
 
saladlam profile image
Salad Lam

Just a point to mention, annotation @org.springframework.transaction.annotation.Transactional is for control the transaction scope of your production code and should not used in test class.