0

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

Java Backend Zero to Hello

📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 7: Testing & DevOps Cơ Bản | Bài 71/86


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 Series

⬅️ Bài trước: PHASE 7: TESTING & DEVOPS CƠ BẢN - Tổng Quan & Mục Tiêu

📋 Lộ trình tổng quan: Xem Toàn Bộ Series

➡️ Bài tiếp theo: BÀI 7.2: MOCKITO


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í