
本文旨在指导开发者如何使用 Mockito 框架在 spring Boot 项目中对 Service 层进行有效的单元测试。通过模拟(Mock)依赖的 DAO 或 Service,可以隔离被测 Service 的复杂性,专注于其自身的业务逻辑验证。本文将提供示例代码和详细步骤,帮助你编写可靠且易于维护的单元测试。
在 spring boot 应用中,Service 层通常依赖于多个 DAO (Data access Object) 或其他的 Service。为了编写有效的单元测试,我们需要隔离被测 Service,避免依赖项的真实行为影响测试结果。Mockito 是一个流行的 Java 模拟框架,可以帮助我们实现这一点。
Mockito 简介
Mockito 允许我们创建模拟对象(Mock Objects),这些对象可以预先设定好行为,例如指定方法调用时的返回值或抛出异常。在单元测试中,我们可以使用 Mockito 来模拟 Service 依赖的 DAO 或其他 Service,从而专注于测试 Service 自身的业务逻辑。
示例代码
假设我们有以下 ExamServiceImpl 类:
@Service public class ExamServiceImpl implements ExamService { private final SubjectService subjectService; private final ScoredAO scoreDAO; private final TeacherDAO teacherDAO; private final StudentDAO studentDAO; @Autowired public ExamServiceImpl(SubjectService subjectService, ScoreDAO scoreDAO, TeacherDAO teacherDAO, StudentDAO studentDAO) { this.subjectService = subjectService; this.scoreDAO = scoreDAO; this.teacherDAO = teacherDAO; this.studentDAO = studentDAO; } @Override public ResponseModel insertScore(RequestModel request) throws IOException { List<TeacherModel> teacher = teacherDAO.getNameList(request); List<StudentModel> student = studentDAO.findStudentList(teacher.get(0).getName(), request.getStudentScore()); String nameStudent = student.get(0).getFirstName() + student.get(0).getLastName(); SubjectModel subject = subjectService.getNameSubject(request, nameStudent); ScoreModel score = new ScoreModel(); score.setStudentName(request.getStudentName()); score.setScore(request.getStudentScore()); score.setSubject(subject.getName); int result = scoreDAO.insert(score); return new ResponseModel(result); } }
现在,我们来编写 ExamServiceImpl 的单元测试:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import java.io.IOException; import java.util.Arrays; import java.util.List; @ExtendWith(MockitoExtension.class) public class ExamServiceImplTest { @Mock private ScoreDAO scoreDAO; @Mock private SubjectService subjectService; @Mock private TeacherDAO teacherDAO; @Mock private StudentDAO studentDAO; @InjectMocks private ExamServiceImpl examService; @Test void insertScoreTest() throws IOException { // 1. 准备 Mock 对象和数据 SubjectModel resFromSubject = new SubjectModel(); resFromSubject.setSubject("Math"); TeacherModel resTeacher = new TeacherModel(); resTeacher.setName("test Teacher"); List<TeacherModel> teacherList = Arrays.asList(resTeacher); StudentModel studentData = new StudentModel(); studentData.setFirstName("firstname"); studentData.setLastName("lastname"); List<StudentModel> studentList = Arrays.asList(studentData); RequestModel request = new RequestModel(); // 2. 设定 Mock 对象的行为 when(teacherDAO.getNameList(any(RequestModel.class))).thenReturn(teacherList); when(studentDAO.findStudentList(anyString(), anyString())).thenReturn(studentList); when(subjectService.getNameSubject(any(RequestModel.class), anyString())).thenReturn(resFromSubject); when(scoreDAO.insert(any(ScoreModel.class))).thenReturn(1); // 3. 执行被测方法 ResponseModel resultTest = examService.insertScore(request); // 4. 验证结果 assertEquals(1, resultTest.getResult()); } }
代码解释
- @ExtendWith(MockitoExtension.class): 这个注解告诉 JUnit 使用 Mockito 扩展,以便自动初始化 Mock 对象。
- @Mock: 这个注解用于创建模拟对象。我们使用 @Mock 注解创建了 ScoreDAO, SubjectService, TeacherDAO, 和 StudentDAO 的模拟对象。
- @InjectMocks: 这个注解用于将模拟对象注入到被测对象中。我们使用 @InjectMocks 注解创建了 ExamServiceImpl 对象,并将上面创建的模拟对象注入到 ExamServiceImpl 中。
- when(…).thenReturn(…): 这个方法用于设定模拟对象的行为。例如,when(scoreDAO.insert(any(ScoreModel.class))).thenReturn(1) 表示当 scoreDAO.insert() 方法被调用时,无论传入什么参数,都返回 1。
- any(ScoreModel.class) 和 anyString(): 这些是 Mockito 提供的参数匹配器,用于匹配任意类型的参数。
- assertEquals(1, resultTest): 这是一个 JUnit 断言,用于验证测试结果是否符合预期。
注意事项
- 确保你的测试类使用了 @ExtendWith(MockitoExtension.class) 注解,以便正确初始化 Mock 对象。
- 使用 @Mock 注解创建模拟对象,并使用 @InjectMocks 注解将它们注入到被测对象中。
- 使用 when(…).thenReturn(…) 方法设定模拟对象的行为。
- 使用 any() 或 anyString() 等参数匹配器来匹配方法参数。
- 使用 JUnit 断言来验证测试结果。
- 为了使测试更具可读性,请遵循AAA模式(Arrange, Act, Assert):
- Arrange (准备): 设置测试环境,包括创建 Mock 对象、设定 Mock 对象的行为、准备测试数据等。
- Act (执行): 调用被测方法。
- Assert (验证): 使用 JUnit 断言来验证测试结果。
总结
通过使用 Mockito,我们可以轻松地对 Spring Boot Service 层进行单元测试。Mockito 允许我们创建模拟对象,并设定它们的行为,从而隔离被测 Service 的复杂性,专注于其自身的业务逻辑验证。编写良好的单元测试可以提高代码质量、减少 bug,并提高代码的可维护性。 记住,良好的单元测试覆盖率是保证软件质量的关键因素之一。


