0

[Java Backend Zero to Hello] [Phase 7] BÀI 7.1: UNIT TEST VỚI JUNIT 5

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


BÀI 7.1: UNIT TEST VỚI JUNIT 5

Mục tiêu

  • Viết Unit Test với JUnit 5
  • Sử dụng Assertions
  • Áp dụng AAA Pattern
  • Test với Parameterized Tests

1. JUNIT 5 LÀ GÌ?

JUnit 5 là framework testing phổ biến nhất cho Java, gồm:

  • JUnit Platform - Nền tảng chạy test
  • JUnit Jupiter - API viết test
  • JUnit Vintage - Chạy test JUnit 3/4

2. DEPENDENCY

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Bao gồm: JUnit 5, Mockito, AssertJ, Spring Test, ...


3. CẤU TRÚC TEST

3.1 Test class cơ bản

import org.junit.jupiter.api.*;

class UserServiceTest {

    @BeforeAll
    static void setupAll() {
        // Chạy 1 lần trước tất cả test
    }

    @BeforeEach
    void setup() {
        // Chạy trước mỗi test
    }

    @AfterEach
    void teardown() {
        // Chạy sau mỗi test
    }

    @AfterAll
    static void teardownAll() {
        // Chạy 1 lần sau tất cả test
    }

    @Test
    void shouldCreateUser() {
        // Test logic
    }

    @Test
    @Disabled  // Bỏ qua test này
    void skippedTest() {
        // ...
    }
}

3.2 DisplayName

@Test
@DisplayName("Should create user successfully when input is valid")
void shouldCreateUser() { ... }

4. ASSERTIONS

4.1 AssertJ (khuyến nghị)

import static org.assertj.core.api.Assertions.*;

@Test
void shouldCreateUser() {
    User user = userService.create(request);

    assertThat(user).isNotNull();
    assertThat(user.getName()).isEqualTo("An");
    assertThat(user.getAge()).isGreaterThan(0);
    assertThat(user.getEmail()).contains("@");
}

4.2 JUnit Assertions

import static org.junit.jupiter.api.Assertions.*;

@Test
void shouldCreateUser() {
    User user = userService.create(request);

    assertNotNull(user);
    assertEquals("An", user.getName());
    assertTrue(user.getAge() > 0);
    assertThrows(NotFoundException.class, () -> userService.findById(999L));
}

4.3 Assert nhiều giá trị

assertAll("user",
    () -> assertEquals("An", user.getName()),
    () -> assertEquals("an@example.com", user.getEmail()),
    () -> assertTrue(user.isActive())
);

5. AAA PATTERN

@Test
void shouldCalculateTotal() {
    // Arrange - Chuẩn bị
    Order order = new Order();
    order.addItem(new Item("Product", 100.0));
    order.addItem(new Item("Product 2", 50.0));

    // Act - Thực hiện
    double total = orderService.calculateTotal(order);

    // Assert - Kiểm tra
    assertThat(total).isEqualTo(150.0);
}

6. ASSUMPTIONS

@Test
void testOnWindows() {
    Assumptions.assumeTrue(System.getProperty("os.name").contains("Windows"));

    // Test chỉ chạy trên Windows
}

@Test
void testOnMac() {
    Assumptions.assumeFalse(System.getProperty("os.name").contains("Mac"));
    // ...
}

7. NESTED TESTS

@DisplayName("User Service Tests")
class UserServiceTest {

    @Nested
    @DisplayName("When creating user")
    class CreateUser {

        @Test
        void shouldSucceedWithValidData() { ... }

        @Test
        void shouldFailWithInvalidEmail() { ... }
    }

    @Nested
    @DisplayName("When finding user")
    class FindUser {

        @Test
        void shouldReturnUserWhenExists() { ... }

        @Test
        void shouldThrowWhenNotFound() { ... }
    }
}

8. PARAMETERIZED TESTS

8.1 @ValueSource

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 5, 10})
void shouldBePositive(int number) {
    assertThat(number).isPositive();
}

@ParameterizedTest
@ValueSource(strings = {"An", "Bình", "Châu"})
void shouldNotBeEmpty(String name) {
    assertThat(name).isNotEmpty();
}

8.2 @CsvSource

@ParameterizedTest
@CsvSource({
    "1, An, 25",
    "2, Bình, 30",
    "3, Châu, 28"
})
void shouldCreateUser(Long id, String name, int age) {
    User user = new User(id, name, age);
    assertThat(user.getId()).isEqualTo(id);
}

8.3 @MethodSource

@ParameterizedTest
@MethodSource("provideUsers")
void shouldValidateUser(User user, boolean expected) {
    assertThat(validator.isValid(user)).isEqualTo(expected);
}

static Stream<Arguments> provideUsers() {
    return Stream.of(
        Arguments.of(new User("An", "an@example.com"), true),
        Arguments.of(new User("", "an@example.com"), false),
        Arguments.of(new User("An", "invalid"), false)
    );
}

9. TEST LIFECYCLE

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserServiceTest {

    @BeforeAll
    void setupAll() {
        // Không cần static
    }
}

10. TEST ORDER

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class UserServiceTest {

    @Test
    @Order(1)
    void shouldCreateUser() { ... }

    @Test
    @Order(2)
    void shouldFindUser() { ... }
}

11. VÍ DỤ HOÀN CHỈNH

@DisplayName("Calculator Service Tests")
class CalculatorServiceTest {

    private CalculatorService calculator;

    @BeforeEach
    void setUp() {
        calculator = new CalculatorService();
    }

    @Test
    @DisplayName("Should add two positive numbers")
    void shouldAdd() {
        assertThat(calculator.add(2, 3)).isEqualTo(5);
    }

    @Test
    @DisplayName("Should throw when dividing by zero")
    void shouldThrowOnDivideByZero() {
        assertThatThrownBy(() -> calculator.divide(10, 0))
            .isInstanceOf(ArithmeticException.class)
            .hasMessage("Cannot divide by zero");
    }

    @ParameterizedTest
    @CsvSource({
        "1, 1, 1",
        "2, 3, 5",
        "10, 20, 30"
    })
    void shouldAddMultiple(int a, int b, int expected) {
        assertThat(calculator.add(a, b)).isEqualTo(expected);
    }
}

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

Bài 1: Test Service

Viết unit test cho UserService với các method CRUD.

Bài 2: Test Exception

Test các trường hợp exception.

Bài 3: Parameterized Test

Test với nhiều input khác nhau.


13. TÓM TẮT

Annotation Mô tả
@Test Đánh dấu method test
@BeforeEach Chạy trước mỗi test
@AfterEach Chạy sau mỗi test
@BeforeAll Chạy 1 lần trước tất cả
@DisplayName Tên hiển thị
@Disabled Bỏ qua test
@Nested Test lồng nhau
@ParameterizedTest Test với nhiều input

Bài tiếp theo: 7.2 Mockito


🧭 Đ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í