单元测试之Mockito
依赖
引入 Mockito 和 JUnit 依赖:
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<!--
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.6.1</version>
<scope>test</scope>
</dependency>
-->
<!-- 如果需要对静态方法进行打桩,使用 mockito-inline 代替 mockito-core -->
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.6.1</version>
<scope>test</scope>
</dependency>
<!-- 引入 MockitoExtension 类 -->
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.6.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0-M1</version>
<scope>test</scope>
</dependency>
IDEA
快捷键:
Ctrl + Shift + T
生成/查找测试类Alt + Insert
->test
生成测试类
查看测试覆盖率:
JUnit
JUnit 注解:
class StudentServiceTest {
@BeforeAll
public static void beforeAll() {
// 所有方法之前执行,只执行一次
}
@AfterAll
public static void afterAll() {
// 所有方法之后执行,只执行一次
}
@BeforeEach
public void beforeEach() {
// 每个方法之前执行
}
@AfterEach
public void afterEach() {
// 每个方法之后执行
}
@Test
void getById() {
// 测试方法
}
}
Mockito
Mockito 注解:
@ExtendWith(MockitoExtension.class)
:使 Mockito 注解生效@InjectMocks
:用于标记测试类,它会将 Mock 对象和 Spy 对象注入测试类@Mock
:虚构一个对象,如果没有打桩,该对象的方法会返回类型的默认值@Spy
:虚构一个对象,如果没有打桩,该对象的方法会调用真实的方法
打桩:通过
when()
定义方法的行为
不要将
@InjectMocks
和@Spy
同时使用,会导致不可预测的结果
@ExtendWith(MockitoExtension.class)
class StudentServiceTest {
@InjectMocks
private StudentService studentService;
@Mock
private StudentDao studentDao;
@Spy
private Random random;
@Test
void getById() {
// 测试方法
}
}
单元测试
单元测试时,我们仅需要对测试类的业务逻辑进行测试,而它的依赖(比如数据库、请求对象或其他中间件)可以通过 mock 来虚构并注入。
@Test
void test1() {
// 未对 StudentDao 进行打桩,默认返回 null
Student student = studentService.getById(1L);
assertNull(student);
}
@Test
public void test2() {
// 打桩,返回学生对象
when(studentDao.getById(1L)).thenReturn(new Student(1L, "小明", 18));
Student student = studentService.getById(1L);
assertNotNull(student);
assertEquals(1L, student.getId());
assertEquals("小明", student.getName());
assertEquals(18, student.getAge());
verify(studentDao).getById(1L); // 验证 studentDao.getById(1L) 被调用 1 次
verify(studentDao, times(1)).getById(1L); // 同上,但可以指定调用次数
}
@Test
public void test3() {
// 打桩,抛出异常
when(studentDao.getById(1L)).thenThrow(new RuntimeException());
assertThrows(RuntimeException.class, () -> studentService.getById(1L));
}
@Test
public void test4() {
// 对静态方法进行打桩
try (MockedStatic<StaticUtils> staticUtils = mockStatic(StaticUtils.class)) {
staticUtils.when(StaticUtils::name).thenReturn("nothing");
assertEquals("nothing", StaticUtils.name());
}
}