使用 Mockito 进行 Java 单元测试
概述
在分层架构中(如 Controller - Service - Mapper),对 Service 层进行单元测试时,通常需要隔离外部依赖,例如数据库。本文介绍如何通过 Mockito 模拟 DAO 层接口,从而实现不连接真实数据库的快速单元测试。
项目结构与依赖
基于 Spring Boot 的标准工程:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
基础模型类
import lombok.Data;
@Data
public class User {
private Long id;
private String username;
private Integer userAge;
}
数据访问层示例
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT id, username, user_age FROM users WHERE id = #{id}")
User findUserById(@Param("id") Long id);
}
业务逻辑层
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userDao;
public boolean checkAdultStatus(Long userId) {
User userInfo = userDao.findUserById(userId);
if (userInfo == null) {
throw new IllegalArgumentException("未找到该用户");
}
return userInfo.getUserAge() >= 18;
}
}
纯 Mockito 单元测试方式
推荐做法:不启动 Spring 上下文,仅使用 Mockito 提供的能力完成测试。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class UserServiceUnitTest {
@Mock
private UserMapper userDaoMock;
@InjectMocks
private UserService targetService;
@Test
void shouldReturnTrueWhenUserIsAdult() {
User testData = new User();
testData.setId(100L);
testData.setUsername("john");
testData.setUserAge(25);
when(userDaoMock.findUserById(100L)).thenReturn(testData);
assertTrue(targetService.checkAdultStatus(100L));
verify(userDaoMock, times(1)).findUserById(100L);
}
@Test
void shouldReturnFalseForMinorUser() {
User minor = new User();
minor.setId(101L);
minor.setUserAge(16);
when(userDaoMock.findUserById(101L)).thenReturn(minor);
assertFalse(targetService.checkAdultStatus(101L));
}
@Test
void shouldThrowExceptionIfUserNotFound() {
when(userDaoMock.findUserById(anyLong())).thenReturn(null);
Exception thrown = assertThrows(IllegalArgumentException.class, () -> {
targetService.checkAdultStatus(999L);
});
assertEquals("未找到该用户", thrown.getMessage());
}
}
结合 Spring 上下文的测试方式
如果确实需要加载部分 Spring 配置,则可以采用如下形式:
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.junit.jupiter.api.Test;
import javax.annotation.Resource;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserServiceIntegrationTest {
@MockBean
private UserMapper mockedDao;
@Resource
private UserService realService;
@Test
void testAdultCheckInSpringContext() {
User sample = new User();
sample.setUserAge(22);
when(mockedDao.findUserById(1L)).thenReturn(sample);
assertTrue(realService.checkAdultStatus(1L));
}
}
关键知识点总结
- @Mock: 创建一个模拟对象替代真实的依赖组件
- @InjectMocks: 将被 @Mock 标记的对象自动装配进目标类实例中
- when(...).thenReturn(...): 定义某个方法在特定参数下调用时应返回的结果
- verify(): 验证某方法是否按预期被调用及其次数
- assertThrows(): 用于断言指定代码块是否会抛出某种类型的异常