0

[Java Backend Zero to Hello] [Phase 7] BÀI 7.3: INTEGRATION TEST

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 7: Testing & DevOps 📖 Nội dung: BÀI 7.3: INTEGRATION TEST 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


BÀI 7.3: INTEGRATION TEST

Mục tiêu

  • Viết Integration Test với Spring Boot
  • Sử dụng Testcontainers
  • Test REST API với MockMvc
  • Test với database thật

1. CÁC LOẠI TEST

Loại Phạm vi Tốc độ
Unit Test Method/class Nhanh
Integration Test Nhiều component Trung bình
E2E Test Toàn bộ hệ thống Chậm

2. @SPRINGBOOTTEST

Load toàn bộ ApplicationContext.

@SpringBootTest
class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldCreateAndFindUser() {
        User user = new User(null, "An", "an@example.com", 25);
        User saved = userService.create(user);

        User found = userService.findById(saved.getId());

        assertThat(found.getName()).isEqualTo("An");
    }
}

3. @WEBMVCTEST

Test riêng Controller layer.

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUser() throws Exception {
        UserResponse user = new UserResponse(1L, "An", "an@example.com", 25, null);

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

        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("An"))
            .andExpect(jsonPath("$.email").value("an@example.com"));
    }

    @Test
    void shouldReturn404WhenNotFound() throws Exception {
        when(userService.findById(999L))
            .thenThrow(new NotFoundException("User not found"));

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

    @Test
    void shouldCreateUser() throws Exception {
        CreateUserRequest request = new CreateUserRequest("An", "an@example.com", 25);
        UserResponse response = new UserResponse(1L, "An", "an@example.com", 25, null);

        when(userService.create(any())).thenReturn(response);

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {
                        "name": "An",
                        "email": "an@example.com",
                        "age": 25
                    }
                    """))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value(1));
    }
}

4. @DATAJPASTEST

Test riêng Repository layer.

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void shouldFindByEmail() {
        User user = new User(null, "An", "an@example.com", 25);
        entityManager.persistAndFlush(user);

        Optional<User> found = userRepository.findByEmail("an@example.com");

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

5. TEST VỚI H2 DATABASE

# application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
@SpringBootTest
@ActiveProfiles("test")
class IntegrationTest {
    // ...
}

6. TESTCONTAINERS

Dùng Docker để chạy database thật (PostgreSQL, MySQL, ...).

6.1 Dependency

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>

6.2 Sử dụng

@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveAndFindUser() {
        User user = new User(null, "An", "an@example.com", 25);
        User saved = userRepository.save(user);

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

        assertThat(found).isPresent();
    }
}

6.3 Container chung

@Testcontainers
public class BaseIntegrationTest {

    @Container
    @ServiceConnection
    protected static final PostgreSQLContainer<?> POSTGRES =
        new PostgreSQLContainer<>("postgres:16");

    @Container
    @ServiceConnection
    protected static final RedisContainer REDIS =
        new RedisContainer(DockerImageName.parse("redis:7"));
}

7. TEST REST API VỚI TESTRESTTEMPLATE

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

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldCreateAndGetUser() {
        CreateUserRequest request = new CreateUserRequest("An", "an@example.com", 25);

        ResponseEntity<UserResponse> createResponse = restTemplate
            .postForEntity("/api/users", request, UserResponse.class);

        assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        Long userId = createResponse.getBody().getId();

        ResponseEntity<UserResponse> getResponse = restTemplate
            .getForEntity("/api/users/" + userId, UserResponse.class);

        assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(getResponse.getBody().getName()).isEqualTo("An");
    }
}

8. @SQL VÀ @SQLGROUP

@SpringBootTest
@Sql(scripts = "/test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
class UserServiceTest {

    @Test
    void shouldFindUser() {
        // Test với data đã load
    }
}

9. TEST PROFILE

@SpringBootTest
@ActiveProfiles("test")
@TestPropertySource(properties = {
    "spring.datasource.url=jdbc:h2:mem:test",
    "logging.level.com.example=DEBUG"
})
class CustomIntegrationTest {
}

10. BÀI TẬP THỰC HÀNH

Bài 1: Controller Test

Test UserController với MockMvc.

Bài 2: Repository Test

Test UserRepository với @DataJpaTest.

Bài 3: Full Integration Test

Test toàn bộ flow với Testcontainers.


11. TÓM TẮT

Annotation Mô tả
@SpringBootTest Load full context
@WebMvcTest Test controller
@DataJpaTest Test repository
@MockBean Mock bean
Testcontainers Docker container
@ActiveProfiles Chọn profile
@Sql Chạy SQL script

Bài tiếp theo: 7.4 Docker cơ bản


🧭 Điều hướng bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí