Mockito 如何处理供应商?

How does Mockito handling Suppliers?

在第一个截图上你可以看到我的测试class。此 class 被注释为 @ExtendWith({MockitoExtension.class}),并且被测试的服务也被注释为 @InjectMocks。在第二个屏幕截图中,您可以看到经过测试的服务。

为什么 Mockito 在这两种情况下都使用 long 供应商?

Mockito 在按以下顺序注入模拟时使用不同的 strategies

  1. 构造函数注入
  2. 属性setter注入
  3. 字段注入

字段注入 在您的示例中不起作用,因为服务的字段被声明为 final.

由于声明了字段 final 并且您显示的代码片段没有字段初始值设定项,因此我假设您有一个带有 Supplier 参数的构造函数。例如

public SomeService(Supplier<String> stringSupplier, Supplier<Long> longTimeSupplier) {
    this.stringSupplier = stringSupplier;
    this.longTimeSupplier = longTimeSupplier;
}

因此 Mockito 将尝试 构造函数注入 ,找到具有两个 Supplier 参数的构造函数并尝试解析参数。

Mockito 然后在测试中找到两个 Supplier mock,但由于类型擦除,它看不到通用类型。因此 Mockito 看到构造函数是这样的:

public SomeService(Supplier stringSupplier, Supplier longTimeSupplier)

Mockito 也无法根据参数名称决定使用哪个 Supplier,因为正常的 Java 反射 API 不提供该信息。所以模拟的名称将不会被考虑在内。

paranamer这样的库可以读取字节码并提取调试信息以读取参数名称,但 Mockito 不使用该库。

因此 Mockito 只注入第一个匹配的模拟,在你的情况下是 Supplier<String> stringSupplier。即使您的问题与泛型有关,当您有两个非泛型的相同类型的参数时,Mockito 也会以相同的方式行事。

我假设你有一个构造函数接受两个 Supplier。所以你可以在你的测试之前调用它。

@BeforeEach
public void setup() {
    service = new SomeService(stringSupplier, longSupplier);
}

如果您无法访问构造函数,例如它具有 包范围 ,您需要使用反射调用它并将可访问的 属性 设置为 true

@BeforeEach
public void setup() throws Exception {
    Constructor<SomeService> constructor = SomeService.class.getConstructor(Supplier.class, Supplier.class);
    constructor.setAccessible(true);
    service = constructor.newInstance(stringSupplier, longSupplier);
}

PS 如果要删除 final,请确保模拟以服务中的字段命名 longTimeSupplierlongSupplier 或者您使用 @Mock(name = "longTimeSupplier").