开发基础知识 - JUnit 5

Introduction

一个简单的例子:

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

class CalculatorTest {

    // @Test 注解表明这是一个测试方法
    @Test
    void testAddition() {
        // 创建被测试类的实例
        Calculator calculator = new Calculator();

        // 执行被测试的方法
        int result = calculator.add(2, 3);

        // 使用断言 (Assertion) 来验证结果是否符合预期
        assertEquals(5, result, "2 + 3 应该等于 5");
    }
}

注解

  • @Test
    它声明了一个方法是一个标准的测试方法。方法应该是 void 类型,方法不应该是 private 的,JUnit 会自动创建测试类的实例来运行这些方法。
  • @BeforeEach & @AfterEach
    这两个注解用于在每个 @Test 方法执行前后设置和清理环境。这对于确保测试之间的独立性至关重要。
  • @BeforeAll & @AfterAll
    这两个注解在当前测试类的所有测试方法运行前后只执行一次。
  • @DisplayName
    默认情况下,测试报告会显示方法名。@DisplayName 允许你提供一个更具描述性的自定义名称。
    比如:@DisplayName("用户名和密码正确时应认证成功")

断言

位于 org.junit.jupiter.api.Assertions下。

  • assertEquals(expected, actual)
  • assertNotEquals(unexpected, actual)
  • assertTrue(boolean condition)
  • assertNull(Object actual)
  • assertArrayEquals(expectedArray, actualArray)
  • assertThrows(expectedType, executable)
@Test
void testDivisionByZero() {
    Calculator calculator = new Calculator();
    // 验证当除数为0时,是否会抛出 ArithmeticException 异常
    assertThrows(ArithmeticException.class, () -> {
        calculator.divide(1, 0);
    });
}

Mockito

为什么需要 Mocking

单元测试强调 隔离性。当你测试一个类 A 时,如果它依赖于另一个复杂的类 B(例如,B 可能需要访问数据库、调用网络接口),你不希望因为 B 的问题(如数据库连接失败)导致 A 的测试失败。
Mocking 技术就是为了解决这个问题。它允许你创建一个 Mock 对象,你可以指定它的方法被调用时应该返回什么值或执行什么操作,从而将测试目标从其依赖项中隔离开来。

Mock

// 被测试的类
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public String getUserGreeting(int userId) {
        String userName = userRepository.findUserNameById(userId);
        if (userName == null) {
            return "Hello, Guest!";
        }
        return "Hello, " + userName + "!";
    }
}

public interface UserRepository {
    String findUserNameById(int userId);
}
@ExtendWith(MockitoExtension.class) // 让 Mockito 注解生效
class UserServiceTest {

    @Mock // 1. 创建一个 UserRepository 的 Mock 对象
    private UserRepository mockUserRepository;

    @InjectMocks // 2. 创建 UserService 实例,并自动注入上面 @Mock 标记的对象
    private UserService userService;

    @Test
    void testGreetingForExistingUser() {
        // 3. Stubbing: 定义 Mock 对象的行为
        // 当调用 mockUserRepository 的 findUserNameById 方法并传入参数 1 时,返回 "Alice"
        when(mockUserRepository.findUserNameById(1)).thenReturn("Alice");

        // 执行测试
        String greeting = userService.getUserGreeting(1);

        // 断言结果
        assertEquals("Hello, Alice!", greeting);
    }
}

Spy

  • Mock:当你创建一个 Mock 对象时,你创建的是一个目标类的“空壳子”。调用 Mock 对象的任何方法,除非你用 when(...).thenReturn(...) 对其行为进行了stubbing,否则它只会返回该方法返回类型的默认值(null、0、false、空集合等)。
  • Spy:当你创建一个 Spy 对象时,你实际上是创建了一个真实的对象实例。调用 Spy 对象的任何方法,默认会直接调用真实对象的原始方法,并返回真实的结果。
class OrderServiceTest {
    private OrderService spyOrderService;

    @BeforeEach
    void setUp() {
        spyOrderService = Mockito.spy(new OrderService());
    }

    @Test
    void testPlaceOrder_Successfully() {        
        // 错误的方式,会抛出 UnsupportedOperationException,因为它先执行了真实的 saveToDatabase 方法
        // when(spyOrderService.saveToDatabase(anyString())).then... 

        // 正确的方式:使用 do...when... 语法
        doNothing().when(spyOrderService).saveToDatabase(anyString());

        String result = spyOrderService.placeOrder(101, 5);
        assertEquals("下单成功", result);
    }
  • when(spy.method()).thenReturn(...) 的形式会先执行真实的方法。
  • doReturn/doThrow/doAnswer 避免执行真实方法带来的副作用。

开发基础知识 - JUnit 5
http://example.com/2025/08/10/JAVA/junit/
Author
Songlin Zhao
Posted on
August 10, 2025
Licensed under