当使用 lombok @RequiredArgsConstructor(onConstructor = @__(@Autowired)) 时,Mockito when().thenReturn() 的行为不符合预期

Mockito when().thenReturn() does not behave as expected when lombok @RequiredArgsConstructor(onConstructor = @__(@Autowired)) is used

我有 Spring class 说

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class MainServiceImpl implements MainService {
    private final InternalService internalService;

    public Set<String> do(String anything) {
      Set<String> relevent = internalService.finaIntern(anything);
      return relevent;
    }
}

我正在编写如下单元测试用例

@RunWith(MockitoJUnitRunner.class)
class TestMainServiceImpl {

    @InjectMocks
    private MainServiceImpl service;

    @Mock
    InternalService internalService;   

    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testDo() { 
        Set<String> setData = new HashSet<>();
        setData.add("ABC");
        String a ="a";
        when(internalService.finaIntern(any(String.class))
                                  .thenReturn(setData);
        Set<String> result = service.do(a);
        assertTrue(!result.isEmpty());
    }

}

这里我的测试用例失败了,但是如果我从 MainServiceImpl 中删除 final 并执行如下所示的显式 @Autowired

@Component
class MainServiceImpl implements MainService {
    @Autowired
    private InternalService internalService;
     .....

在这里我很想知道 1.如果我删除final关键字,我的测试用例如何通过 2. 使用 @RequiredArgsConstructor 是一种好习惯吗?如果是,那么如何使用?如果不是,那么为什么?

提前致谢

lombok和Spring@Autowired无关 @RunWith(MockitoJUnitRunner.class)MockitoAnnotations.initMocks(this); 的组合就是问题所在。删除其中任何一个,行为就如预期的那样。你不需要他们两个。事实上 MockitoAnnotations.initMocks(this); 只存在于你不能使用 @RunWith(MockitoJUnitRunner.class) 的情况下,例如如果你需要使用 SpringRunner.class.

这就是它不起作用的原因。 首先实例化所有对象。所以你的 @Mock 被创建并注入你的 @InjectMock object:

下面可以看到,injectIntomock里面的new create mock(mocks[0]),service是同一个对象

但是第二次初始化发生了。 因此 mockito 创建一个新的 @Mock 对象,并尝试将其注入到已经实例化的 @InjectMock 对象中。但是只要它是最终的,就无法将它注入到该领域。 所以这是我们在第二次初始化后得到的:

如您所见,现在 testClassInstance 中的模拟对象与注入到被测对象的模拟对象不同。

@RequiredArgsConstructor呢:对我来说,按照你的方式使用它是完全可以的。

你的测试用例应该是这样的。你不应该使用

MockitoAnnotations.initMocks(this)

当您将 @InjectMock 用于被测 class 时。这适用于@AllArgConstructor。

  @RunWith(MockitoJUnitRunner.class)
    class TestMainServiceImpl {

    @InjectMocks
    private MainServiceImpl service;

    @Mock
    InternalService internalService;   


    @Test
    public void testDo() { 
        Set<String> setData = new HashSet<>();
        setData.add("ABC");
        String a ="a";
        when(internalService.finaIntern(any(String.class))
                                  .thenReturn(setData);
        Set<String> result = service.do(a);
        assertTrue(!result.isEmpty());
    }

}

试试上面的方法为@allArgConstructor 和@RequiredArgConstructor 编写测试用例。如果这个剂量有效,你可以试试下面的逻辑。

如果您在 class 中仅使用 @Data 注释 (lombok.Data),则将生成仅具有必填(最终)字段的构造函数。这是因为 Data 隐含了 RequiredArgsConstructor。示例:

@Data
@Component
class MainServiceImpl implements MainService {
private final InternalService internalService;
}

注解@Builder 为我们生成 MainServiceImpl class 帮助我们创建新的 MainServiceImpl

@Data
@Builder
@Component
class MainServiceImpl implements MainService {
private final InternalService internalService;
}