我想为 spring boot 服务方法编写一个 mockito 测试用例

Posted

技术标签:

【中文标题】我想为 spring boot 服务方法编写一个 mockito 测试用例【英文标题】:I want to write a mockito test case for a spring boot service method 【发布时间】:2022-01-08 21:03:45 【问题描述】:

这是服务方法。

    @Override
    public void removeStudentByID(Long studentId) throws StudentExistsException 
        boolean exists = studentRepo.existsById(studentId);
        if (!exists) 
            throw new StudentExistsException("Student doesn't exist");
        
        studentRepo.deleteById(studentId);
    

下面是我为它编写的测试用例。

@Test
    public void removeStudentByIdTest() throws StudentExistsException 
        Student student = new Student(1, "Drake", "drake@gmail.com");
        when(studentRepo.save(student)).thenReturn(student);
        studentService.removeStudentByID(student.getId());
        assertEquals(false, studentRepo.existsById(student.getId()));
    

它没有运行... 但是下面的代码有效。

    public void removeStudent(String email) 
        Student student = studentRepo.findByEmail(email);
        studentRepo.delete(student);
    
@Test
    public void removeStudentTest() throws StudentExistsException 
        Student student = new Student(3, "Ben", "ben@gmail.com");
        studentRepo.save(student);
        studentService.removeStudent(student.getEmail());
        assertEquals(false, studentRepo.existsById(student.getId()));
    

所以,作为一个菜鸟,我有点困惑如何让它在第一个中起作用。或者他们两个都有问题。

【问题讨论】:

嗨!要测试***方法,您将(正确连接您的模拟,然后)when(studentRepoMock.existsById()).thenReturn(false)(在一个测试中,true 在另一个测试中)... ..当false:断言/预期异常,当trueverify(studentRepoMock.deleteById(1).. 【参考方案1】:

要测试这段代码:

@Service
public class StudentService  
  @Override
  public void removeStudentByID(Long studentId) throws StudentExistsException 
    boolean exists = studentRepo.existsById(studentId);
    if (!exists) 
        throw new StudentExistsException("Student doesn't exist");
    
    studentRepo.deleteById(studentId);
  

我们可以像这样模拟/“单元测试”:

package com.example.demo;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class StudenServiceTests  // we only test StudentService, since we (sort of) trust studentRepository (tested it elsewehere/how)

  @Mock // A Mock! (alternatively @MockBean)
  private StudentRepository studentRepoMock;
  @InjectMocks // A real service (with mocks injected;)!!! (alternatively @Autowired/...)
  private StudentService testee; // this guy we want to test (to the bone!)

  @Test // ..the "good case"
  void removeStudentByIdExists() throws StudentExistsException 
    // Given:
    // we don't even need a student, only his id
    // instruct mock for the "first interaction" (see testee code):
    when(studentRepoMock.existsById(1L)).thenReturn(true);
    // instruct mock for "second interaction" (see testee code):
    doNothing().when(studentRepoMock).deleteById(1L); // we could also use ...delete(any(Long.class))  or ..anyLong();

    // When: (do it!)
    testee.removeStudentByID(1L);

    // Then: Verify (interactions, with as exact as possible parameters ..Mockito.any***)
    verify(studentRepoMock).existsById(1L); //.times(n) ...when you had a list ;)
    verify(studentRepoMock).deleteById(1L);
  

  @Test // .. the "bad (but correct) case"/exception
  void removeStudentByIdNotExists() throws StudentExistsException 
    // Given:
    when(studentRepoMock.existsById(1L)).thenReturn(false);

    // When:
    StudentExistsException thrown = assertThrows(
            StudentExistsException.class,
            () -> testee.removeStudentByID(1L),
            "Expected testee.removeStudentByID to throw, but it didn't");
    // Then:
    assertNotNull(thrown);
    assertTrue(thrown.getMessage().contains("Student doesn't exist"));
  

...使用mvn test 运行。

通过这两个测试用例,我们为给定的方法/服务实现了 100% 的测试(和分支)覆盖率


但不要将其与“集成测试”(我们使用真正的 repo/数据库)混为一谈,那么我们可以使用:

JDBC Testing Support Test Annotations (@Commit, @Rollback, @Sql*) Jdbc模板 ...

“默认”(假设为空)数据库的示例,已准备好(和@Rollback):

package com.example.demo;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;

@SpringBootTest
class StudentServiceIT  // .. here we try to test "all of our application" (as good as possible ..to the bone)

  @Autowired //! real "bean", no mock... this is only auxilary for this test, we could use some other method to prepare & verify
  private StudentRepository studentRepository;
  @Autowired //!! "real service"
  private StudentService studentService;

  @Test
  @Rollback // actually our test is "self-cleaning"...when no exceptions ;)
  void removeStudentByIdExists() throws StudentExistsException 
    // Given:
    Student student = new Student(1L, "Drake", "drake@gmail.com");
    studentRepository.save(student);

    // When:
    studentService.removeStudentByID(1L);

    // Then:
    assertFalse(studentRepository.findById(1L).isPresent());
  

  @Test
  @Rollback // in any case (if assumptions were wrong;)
  void removeStudentByIdNotExists() throws StudentExistsException 
    // Given:
    // nothing, we assume "empty db"

    // When:
    StudentExistsException thrown = assertThrows(
            StudentExistsException.class,
            () -> studentService.removeStudentByID(1L),
            "Expected testee.removeStudentByID to throw, but it didn't");
    // Then:
    assertNotNull(thrown);
    assertTrue(thrown.getMessage().contains("Student doesn't exist"));
  

(运行:mvn failsafe:integration-test

感谢/参考:

JUnit 5: How to assert an exception is thrown? Mockito test a void method throws an exception Difference between @Mock and @InjectMocks

Complete Sample@github

【讨论】:

非常感谢。我为此苦苦挣扎了两天。我对此很陌生。即使我不了解您的代码的所有方面,我保证我会深入了解它,因为我已经理解了它的要点。我不能感谢你。没有其他人帮助过我。 欢迎您! (young:) "padawan" :) 我添加了更多的 cmets(为了代码,在帖子中;)...【参考方案2】:

我就是这样做的,让我知道它是否很好。 :)

@Test
    public void removeStudentByIdTest() throws StudentExistsException 
        Student student = new Student(1, "Drake", "drake@gmail.com");
        when(studentRepo.existsById(student.getId())).thenReturn(true);
        studentService.removeStudentByID(student.getId());
        verify(studentRepo).deleteById((long) 1);
    

【讨论】:

以上是关于我想为 spring boot 服务方法编写一个 mockito 测试用例的主要内容,如果未能解决你的问题,请参考以下文章

如何理解 Spring Boot 应用程序已准备好工作?

带有 Docker 的 Spring Boot 2.3。当我在同一个存储库中有多个 Spring Boot 应用程序时,我想为特定应用程序构建映像

Spring Boot应用程序即服务linux [关闭]

Spring Boot - 控制器测试失败并出现 404 代码

Spring Boot 控制器单元测试:无法加载 ApplicationContext

Spring Boot REST API - 请求超时?