Mockito 中的间谍和带委托的模拟之间的区别

Difference between a spy and a mock with delegation in Mockito

我正在为从 Kafka 获取特定对象的应用程序编写集成测试,验证它们,执行各种转换并将转换后的对象保存到另一个数据库或将错误响应发送回 Kafka。

生产数据库有几个命名查询映射到相应 JpaRepository 中的相关方法,如下所示:

public interface PositionRepository extends JpaRepository<Position, Long> {

  @Procedure(name = "generatePositionCode")
  void generatePositionCode(@Param("clientCode") Integer clientCode);
//remainder omitted

我正在使用 Mockito 框架和 H2 内存数据库进行集成测试。由于测试数据库不包含此类命名查询,我想在存储库中存根相应的方法并且在调用它们时不执行任何操作(这对于测试目的是可以的),但继续调用存储库的其他非存根方法。

我一直认为这是你应该在 Mockito 中使用间谍的时候。但是,当我这样定义间谍时:

@Spy
private PositionRespository positionRepository;

public void setUp() {
    doNothing().when(positionRepository).generatePositionCode(anyInt());
}

我的集成测试失败,异常归结为:

Caused by: org.h2.jdbc.JdbcSQLException: Database "FT" not found; SQL statement: call FT.Ftposgn.generatePositionCode(?) [90013-197]

如果另一方面,我在配置 class 中定义一个带委托的模拟,如下所示:

@Primary
@Bean
public PositionRepository mockPositionRepository(PositionRepository positionRepository) {
    return Mockito.mock(PositionRepository.class, 
                        AdditionalAnswers.delegatesTo(positionRepository));
}

并在测试中自动装配存储库 class:

@Autowired
private PositionRepository positionRepository;

public void setUp() {
    doNothing().when(positionRepository).generatePositionCode(anyInt());
}

所有测试都是绿色的。

我一直在努力思考为什么这两种方法会产生不同的结果但不能。谁能解释一下?

谢谢。

在第一种情况下,您应该在 PositionRepository 上使用 @Spy 而不是 PersonRepository。 如果您正在测试 PositionRepository 并想模拟其 class 的方法,那么您应该在 PositionRepository 上创建 Spy。如果你正在测试 PositionRepository 并且想模拟另一个 class 比如 PersonRepository 那么你应该在 PersonRepository 上制作 Mock 换句话说:Spy 是部分模拟,Mock 是完全模拟。如果你在对象上创建 Spy 并且不提供 when().thenReturn() 那么它将尝试使用现有的实现。

我设法解决了问题。

第一个问题是我使用的是 @Spy 注释而不是 @SpyBean 注释,因此间谍不作为 bean 由 Spring 管理。

下一个陷阱是 Spring Data JPA 使用 final 方法将存储库的实现创建为 CGLIB 代理,因此不能仅使用 Mockito 核心库创建 Spy。您必须在类路径上添加 org.mockito:mockito-inline 依赖项。

最后的考虑是 Mockito.verify() 和其他此类方法将失败并返回 MockitoException 因为它们将在目标对象而不是 Spy 上调用。为了克服这个问题,创建一个 Spy as

@SpyBean(proxyTargetAware = false)

在这种情况下,对 verify() 的调用将直接使用代理而不是 AOP 建议 bean 的目标。