Skillversitas

Testing Spring Boot

Why Is Testing Important?

Testing ensures the code works correctly, makes refactoring easier, and prevents regressions. Spring Boot ships with many built-in testing tools.

Types of Tests

Unit Test        → Test 1 class, mock dependencies
Integration Test → Test several components together
End-to-End Test  → Test the entire application

@SpringBootTest

Loads the full application context for integration tests:

@SpringBootTest
class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
    }

    @Test
    void shouldCreateUser() {
        UserRequestDTO request = new UserRequestDTO();
        request.setName("Mike");
        request.setEmail("[email protected]");

        UserResponseDTO response = userService.createUser(request);

        assertThat(response.getId()).isNotNull();
        assertThat(response.getName()).isEqualTo("Mike");
        assertThat(response.getEmail()).isEqualTo("[email protected]");
    }

    @Test
    void shouldReturnAllUsers() {
        UserRequestDTO req1 = new UserRequestDTO();
        req1.setName("Mike");
        req1.setEmail("[email protected]");

        UserRequestDTO req2 = new UserRequestDTO();
        req2.setName("John");
        req2.setEmail("[email protected]");

        userService.createUser(req1);
        userService.createUser(req2);

        List<UserResponseDTO> users = userService.getAllUsers();

        assertThat(users).hasSize(2);
    }
}

@WebMvcTest

Tests the controller only, without loading the full service/repository:

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUser() throws Exception {
        UserResponseDTO user = new UserResponseDTO();
        user.setId(1L);
        user.setName("Mike");
        user.setEmail("[email protected]");

        when(userService.getUserById(1L)).thenReturn(user);

        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("Mike"))
            .andExpect(jsonPath("$.email").value("[email protected]"));
    }

    @Test
    void shouldReturn404WhenUserNotFound() throws Exception {
        when(userService.getUserById(99L))
            .thenThrow(new RuntimeException("User not found"));

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

@DataJpaTest

Tests the repository layer with an embedded database:

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveAndFindUser() {
        User user = new User();
        user.setName("Mike");
        user.setEmail("[email protected]");

        User saved = userRepository.save(user);
        Optional<User> found = userRepository.findById(saved.getId());

        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("Mike");
    }

    @Test
    void shouldFindByEmail() {
        User user = new User();
        user.setName("John");
        user.setEmail("[email protected]");
        userRepository.save(user);

        Optional<User> found = userRepository.findByEmail("[email protected]");

        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("John");
    }
}

@MockBean

Creates a mock bean that replaces the real bean in the Spring context:

@SpringBootTest
class OrderServiceTest {

    @MockBean
    private ProductRepository productRepository;

    @Autowired
    private OrderService orderService;

    @Test
    void shouldCreateOrder() {
        Product product = new Product();
        product.setId(1L);
        product.setName("Laptop");
        product.setStock(10);

        when(productRepository.findById(1L)).thenReturn(Optional.of(product));

        Order order = new Order();
        order.setProductId(1L);
        order.setQuantity(2);

        Order created = orderService.createOrder(order);

        assertThat(created.getId()).isNotNull();
        verify(productRepository).save(any(Product.class));
    }
}

AssertJ Assertions

Spring Boot bundles AssertJ. Usage examples:

@Test
void assertExamples() {
    List<String> names = List.of("Mike", "John", "Anna");

    // String
    assertThat("Hello").isNotEmpty();
    assertThat("Hello").startsWith("He").endsWith("lo");

    // Collection
    assertThat(names).hasSize(3);
    assertThat(names).contains("Mike", "John");
    assertThat(names).doesNotContain("Diana");

    // Object
    User user = new User();
    user.setName("Mike");
    assertThat(user).isNotNull();
    assertThat(user.getName()).isEqualTo("Mike");

    // Exception
    assertThatThrownBy(() -> {
        throw new RuntimeException("Error");
    }).isInstanceOf(RuntimeException.class)
      .hasMessageContaining("Error");
}

Unit Test vs Integration Test

Aspect Unit Test Integration Test
Annotation @WebMvcTest, @DataJpaTest @SpringBootTest
Speed Very fast (< 1 second) Slower (a few seconds)
Database Mock / Embedded Embedded / Real
Test annotation @MockBean @Autowired
Coverage One component Several components
Spring Context Partial Full

Testing Tips

  1. Consistent naming: methodName_shouldExpectedBehavior_whenCondition
  2. One aspect per test: don't test many things in one test
  3. Use @BeforeEach: for the same setup
  4. Test edge cases: null, empty, boundary values
  5. Use verify(): to make sure a method was called
@Test
void createUser_shouldThrowException_whenEmailExists() {
    UserRequestDTO request = new UserRequestDTO();
    request.setName("Mike");
    request.setEmail("[email protected]");

    when(userRepository.existsByEmail("[email protected]")).thenReturn(true);

    assertThatThrownBy(() -> userService.createUser(request))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("Email already in use");
}

Testing makes your application reliable and easy to extend. In the next chapter, we'll learn about Spring Boot Actuator for production monitoring.