Mockito 中模拟对象的自动装配字段 returns null

Autowired fields of mocked object returns null in Mockito

我有一个StudentServiceclass。并且我写了一个class用于StudentServiceclass的单元测试方法。我的代码如下:-

@Component
@EnableAutoConfiguration
public class StudentService {

@Autowired
StudentInstitutionMapper studentInstitutionMapper;

public Integer getPresentStudentCount(StudentParams studentParam) {
    // TODO Auto-generated method stub
    StudentInstitutionExample example = new StudentInstitutionExample();
    StudentInstitutionExample.Criteria criteria = example.createCriteria();
    criteria.andActiveYnEqualTo("Y");
    criteria.andDeleteYnEqualTo("N");
    criteria.andIsPresentEqualTo("Y");
    criteria.andInstitutionTestIdEqualTo(studentParam.getInstitutionTestId());
    List<StudentInstitution> studentInstitutionList = studentInstitutionMapper.selectByExample(example);//line 8 in method
    return studentInstitutionList.size();
}


}

并且在我的单元测试中class,我写了下面的方法。

    @Test
    public void testStudentService_getPresentStudentCount1()
    {



        StudentService service=new StudentService();
        StudentParams studentParam=mock(StudentParams.class);

        Integer institutionTestId=3539;
        when(studentParam.getInstitutionTestId()).thenReturn(institutionTestId);


        int i=service.getPresentStudentCount(studentParam);
        assertEquals(0,i);

    }

当我执行测试 class 时,出现错误。这是因为在StudentServiceclass中,在getPresentStudentCount()方法中,在第8行,studentInstitutionMapper字段是null。这只发生在模拟对象上。我如何获取模拟对象的自动装配字段?

在你的测试 class 中尝试像这样声明对象 studentInstitutionMapper

@Mock
StudentInstitutionMapper studentInstitutionMapper;

您可以使用@Mock 注释注入自动装配class。在许多情况下,您应该使用 @InjectMocks 注释创建测试 class 实例,多亏了这个注释,您的模拟可以直接注入。

@RunWith(PowerMockRunner.class)
@PrepareForTest({StudentService.class})
public class StudentServiceTest {

@InjectMocks
StudentService service;

@Mock
StudentInstitutionMapper studentInstitutionMapper;

@Test
public void testStudentService_getPresentStudentCount1()
{
    MockitoAnnotations.initMocks(this);

    StudentParams studentParam=mock(StudentParams.class);

    Integer institutionTestId=3539;
    when(studentParam.getInstitutionTestId()).thenReturn(institutionTestId);


    int i=service.getPresentStudentCount(studentParam);
    assertEquals(0,i);


}

这有助于更好地解释:Difference between @Mock and @InjectMocks

您需要在单元测试中使用@InjectMocks 注释:

@ExtendWith(MockitoExtension.class)
class StudentServiceTest {

    @Mock
    private StudentInstitutionMapper studentInstitutionMapper;

    @InjectMocks
    private StudentService studentService;

    @Test
    public void testStudentService_getPresentStudentCount1() {


        StudentParams studentParam = mock(StudentParams.class);

        Integer institutionTestId = 3539;
        when(studentParam.getInstitutionTestId()).thenReturn(institutionTestId);


        int i = studentService.getPresentStudentCount(studentParam);
        assertEquals(0, i);

    }
}

您还应该在单元测试中配置 studentInstitutionMapper 的行为,使其return达到预期的结果。

有一种简单的解决方案不涉及 mockito 的高级注释:

您可以像这样重构 StudentService

public class StudentService {

    private final StudentInstitutionMapper studentInstitutionMapper;

    public StudentService(StudentInstitutionMapper studentInstitutionMapper) {
       this.studentInstitutionMapper = studentInstitutionMapper;
    }
}

这个解决方案是我最喜欢的解决方案,因为当我在测试中创建 StudentService 时,我确切地看到它需要什么依赖项,它们的类型。因此,即使不打开 StudentService class.

的源代码,我也可以提供 mocks/real 实现

这种类型的注入(构造函数注入,而不是您在问题中使用的字段注入)的另一个好处是,不会破坏字段的封装。

备注:

  1. 我没有把 @Autowired 放在构造函数上,因为在 spring 的最新版本中只要有一个构造函数就不需要它(对于单元测试它是无关紧要的完全没有)。

  2. 如果您担心构造函数的样板代码,您可以使用 Lombok 并放置一个注释来为您生成全参数构造函数。结合注释 1,这允许完全删除构造函数代码

P.S。我不打算在这里开始字段注入与构造函数注入的 "holy-war",我只是在说明这种方法,因为之前没有人在其他答案中提到过它,从技术上讲它解决了问题中提出的问题。欢迎google讨论这个话题。