Spring Boot/Testing & Deploy/Testing trong Spring Boot
1/2
~20 phútTesting & Deploy

Testing trong Spring Boot

Unit test, Integration test với Spring Boot Test, MockMvc, TestRestTemplate và best practices testing.

Tại sao testing quan trọng?

Code không có test giống như xây nhà không có móng — trông ổn cho đến khi có thay đổi. Trong Spring Boot, framework hỗ trợ testing rất mạnh.

Các tầng test

┌─────────────────────────────┐
│     E2E Test (ít nhất)      │  Selenium, Playwright
├─────────────────────────────┤
│   Integration Test (vừa)    │  @SpringBootTest
├─────────────────────────────┤
│   Unit Test (nhiều nhất)    │  JUnit + Mockito
└─────────────────────────────┘

Unit Test với JUnit 5

Test một class độc lập, mock tất cả dependencies:

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void shouldReturnUserWhenFound() {
        // Given
        User user = new User(1L, "Alice", "[email protected]");
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        // When
        User result = userService.findById(1L);

        // Then
        assertThat(result.getName()).isEqualTo("Alice");
        verify(userRepository).findById(1L);
    }

    @Test
    void shouldThrowWhenUserNotFound() {
        when(userRepository.findById(99L)).thenReturn(Optional.empty());

        assertThrows(UserNotFoundException.class,
            () -> userService.findById(99L));
    }
}

💡 Given-When-Then

Cấu trúc test rõ ràng: Given (setup), When (action), Then (verify). Giúp test dễ đọc và maintain.

Integration Test với @SpringBootTest

Test toàn bộ Spring context:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private UserRepository userRepository;

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

    @Test
    void shouldCreateUser() {
        // Given
        CreateUserRequest request = new CreateUserRequest("Alice", "[email protected]");

        // When
        ResponseEntity<User> response = restTemplate.postForEntity(
            "/api/users", request, User.class);

        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody().getName()).isEqualTo("Alice");
        assertThat(userRepository.count()).isEqualTo(1);
    }
}

Testing API với MockMvc

Test controller layer mà không cần start full server:

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUser() throws Exception {
        User user = new User(1L, "Alice", "[email protected]");
        when(userService.findById(1L)).thenReturn(user);

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

    @Test
    void shouldReturn404WhenNotFound() throws Exception {
        when(userService.findById(99L))
            .thenThrow(new UserNotFoundException(99L));

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

Test Database với @DataJpaTest

Test chỉ JPA layer, nhẹ hơn @SpringBootTest:

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldFindByEmail() {
        userRepository.save(new User(null, "Alice", "[email protected]"));

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

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

Best Practices

  1. Test behavior, không test implementation — test "kết quả" chứ không test "cách làm"
  2. Mỗi test độc lập — không phụ thuộc thứ tự chạy
  3. Tên test mô tả rõ ýshouldThrowWhenEmailInvalid > test1
  4. Unit test nhanh, integration test kỹ — unit test chạy ms, integration test chạy giây
  5. Test cả happy path lẫn edge case — đừng chỉ test trường hợp thành công