在 Java 中正确使用 mockito ArgumentCaptor?
Proper usage of mockito ArgumentCaptor in Java?
我看过几个使用示例,例如Using Mockito ArgumentCaptor,但我对以下场景的正确用法感到困惑:
这是我要测试的方法:
@Override
public ProductDTO create(Product product, UUID uuid) {
// code omitted for brevity
final Product saved = productRepository.save(product);
final Currency currency = currencyService.getCurrencyByUuid(uuid);
return new ProductDTO(saved, currency);
}
我的测试方法:
@RunWith(MockitoJUnitRunner.class)
public class ProductServiceImplTest {
@Mock
private ProductRepository productRepository;
@Mock
private CurrencyService currencyService;
@InjectMocks
private ProductServiceImpl productService;
@Captor
private ArgumentCaptor<Product> productCaptor;
@Test
public void test_create() {
UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000001");
UUID productUuid = UUID.fromString("00000000-0000-0000-0000-000000000222");
Currency currency = Currency.getInstance("EUR");
Product product = new Product();
productCostBySite.setProductUuid(productUuid);
// 1. prepare the values
when(productRepository.save(productCaptor.capture())).thenReturn(product);
when(currencyService.getCurrencyByUuid(uuid)).thenReturn(currency);
// 2. call the service method that is tested
ProductDTO result = productService.create(product);
// 3. Check if the captured value is the expected one
Product value = productCaptor.getValue();
assertEquals(productUuid, value.getProductUuid());
assertEquals(currency.getCurrencyCode(),
result.getCurrency().getCurrencyCode());
}
}
题目如下(数字也是测试方法的评论编号):
1. 我使用 ArgumentCaptor
和 currencyService.getCurrencyByUuid(uuid)
通过返回值测试了 productRepository.save
方法,因为它不是存储库调用。是真的吗?
2.我调用了服务方法,但是在某些例子中使用了verify
方法。我不确定这个例子是否需要使用 verify
方法?有什么想法吗?
3. 我将返回值用于服务,将捕获值用于存储库。这是正确的做法吗?
对我来说,您根本不需要 ArgumentCaptor
,您已经模拟了您的存储库和方法 (create
) returns 和 ProductDTO
。因此,测试此方法有点微不足道。
Product emptyProduct = new Product();
Product productWithValues = new Product();
productWithValues.set... // set the values you want to assert when(productRepository.save(Mockito.eq(emptyProduct))).thenReturn(productWithValues);
Currency currency = new Currency();
currency.set... // more fields you would like to assert
UUID uuid = UUID.randomUUID();
when(currencyService.getCurrencyByUuid(uuid)).thenReturn(currency);
ProductDTO result = create(emptyProduct, id);
Mockito.assertEquals(result.getCurrency().getCurrencyCode(), currency.getCurrencyCode());
Mockito.assertEquals(result.getProduct().getProductUuid(), product.getProductUuid());
声明一下,我没有编译上面的代码,但你应该明白了。
根据documentation推荐的使用ArgumentCaptor的方法不是为了存根,而是为了验证:
verify(productRepository).save(productCaptor.capture())
Argument captor 对于所提供的 create(...)
实现看起来不是特别有用,因为如果您保存的对象与您传递给测试方法的对象完全相同,然后将实际参数传递给 when(...)
足以模拟:
when(productRepository.save(product)).(...)
根据我的经验,当参数未显式传递给被测方法时,更多地使用参数捕获器(在您的示例中,假设 productRepository#save
没有保存传递给 create(...)
的产品,而是在 create(...)
本身内部创建的一些新实例)或者当被测方法更改某些未包含在对象的 equals(...)
方法中的字段时。
应在模拟对象上使用 Verify 来断言实际方法调用使用所需参数发生了特定次数。
具体回答您的问题:
- 如果传递给
productRepository#save
的产品与传递给 create(...)
的产品相同,则不需要参数捕获器,因为您只需将 when(productRepository.save(productCaptor.capture()))
替换为 when(productRepository.save(product))
- 测试下的方法应该在没有
verify(...)
的情况下被调用,verify 旨在与模拟一起使用以断言特定的模拟方法被调用
- 总体方法是正确的
Eugene 是对的,你不需要 ArgumentCaptor
。回答您何时需要它的问题:如果您想验证您在方法之前或之后无法访问的转换对象的值。
例如,在
boolean createAndSave(String productName) {
Product p = new Product(productName);
return repository.save(p);
}
您无权访问已创建的 Product
但想验证它是否已正确创建。为此,您可以捕获它:
ArgumentCaptor<Product> savedCaptor = ArgumentCaptor.forClass(Product.class);
service.createAndSave("name");
verify(repository).save(savedCaptor.capture()); // get the product being saved
Product saved = savedCaptor.getValue();
assertEqual("name", saved.getName());
现在,您要确保使用存储库保存了您为服务指定的名称的产品。
在您的情况下,您可以将 when(repository.save(any()).thenReturn(product)
与您提前创建的产品一起使用。
我看过几个使用示例,例如Using Mockito ArgumentCaptor,但我对以下场景的正确用法感到困惑:
这是我要测试的方法:
@Override
public ProductDTO create(Product product, UUID uuid) {
// code omitted for brevity
final Product saved = productRepository.save(product);
final Currency currency = currencyService.getCurrencyByUuid(uuid);
return new ProductDTO(saved, currency);
}
我的测试方法:
@RunWith(MockitoJUnitRunner.class)
public class ProductServiceImplTest {
@Mock
private ProductRepository productRepository;
@Mock
private CurrencyService currencyService;
@InjectMocks
private ProductServiceImpl productService;
@Captor
private ArgumentCaptor<Product> productCaptor;
@Test
public void test_create() {
UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000001");
UUID productUuid = UUID.fromString("00000000-0000-0000-0000-000000000222");
Currency currency = Currency.getInstance("EUR");
Product product = new Product();
productCostBySite.setProductUuid(productUuid);
// 1. prepare the values
when(productRepository.save(productCaptor.capture())).thenReturn(product);
when(currencyService.getCurrencyByUuid(uuid)).thenReturn(currency);
// 2. call the service method that is tested
ProductDTO result = productService.create(product);
// 3. Check if the captured value is the expected one
Product value = productCaptor.getValue();
assertEquals(productUuid, value.getProductUuid());
assertEquals(currency.getCurrencyCode(),
result.getCurrency().getCurrencyCode());
}
}
题目如下(数字也是测试方法的评论编号):
1. 我使用 ArgumentCaptor
和 currencyService.getCurrencyByUuid(uuid)
通过返回值测试了 productRepository.save
方法,因为它不是存储库调用。是真的吗?
2.我调用了服务方法,但是在某些例子中使用了verify
方法。我不确定这个例子是否需要使用 verify
方法?有什么想法吗?
3. 我将返回值用于服务,将捕获值用于存储库。这是正确的做法吗?
对我来说,您根本不需要 ArgumentCaptor
,您已经模拟了您的存储库和方法 (create
) returns 和 ProductDTO
。因此,测试此方法有点微不足道。
Product emptyProduct = new Product();
Product productWithValues = new Product();
productWithValues.set... // set the values you want to assert when(productRepository.save(Mockito.eq(emptyProduct))).thenReturn(productWithValues);
Currency currency = new Currency();
currency.set... // more fields you would like to assert
UUID uuid = UUID.randomUUID();
when(currencyService.getCurrencyByUuid(uuid)).thenReturn(currency);
ProductDTO result = create(emptyProduct, id);
Mockito.assertEquals(result.getCurrency().getCurrencyCode(), currency.getCurrencyCode());
Mockito.assertEquals(result.getProduct().getProductUuid(), product.getProductUuid());
声明一下,我没有编译上面的代码,但你应该明白了。
根据documentation推荐的使用ArgumentCaptor的方法不是为了存根,而是为了验证:
verify(productRepository).save(productCaptor.capture())
Argument captor 对于所提供的 create(...)
实现看起来不是特别有用,因为如果您保存的对象与您传递给测试方法的对象完全相同,然后将实际参数传递给 when(...)
足以模拟:
when(productRepository.save(product)).(...)
根据我的经验,当参数未显式传递给被测方法时,更多地使用参数捕获器(在您的示例中,假设 productRepository#save
没有保存传递给 create(...)
的产品,而是在 create(...)
本身内部创建的一些新实例)或者当被测方法更改某些未包含在对象的 equals(...)
方法中的字段时。
应在模拟对象上使用 Verify 来断言实际方法调用使用所需参数发生了特定次数。
具体回答您的问题:
- 如果传递给
productRepository#save
的产品与传递给create(...)
的产品相同,则不需要参数捕获器,因为您只需将when(productRepository.save(productCaptor.capture()))
替换为when(productRepository.save(product))
- 测试下的方法应该在没有
verify(...)
的情况下被调用,verify 旨在与模拟一起使用以断言特定的模拟方法被调用 - 总体方法是正确的
Eugene 是对的,你不需要 ArgumentCaptor
。回答您何时需要它的问题:如果您想验证您在方法之前或之后无法访问的转换对象的值。
例如,在
boolean createAndSave(String productName) {
Product p = new Product(productName);
return repository.save(p);
}
您无权访问已创建的 Product
但想验证它是否已正确创建。为此,您可以捕获它:
ArgumentCaptor<Product> savedCaptor = ArgumentCaptor.forClass(Product.class);
service.createAndSave("name");
verify(repository).save(savedCaptor.capture()); // get the product being saved
Product saved = savedCaptor.getValue();
assertEqual("name", saved.getName());
现在,您要确保使用存储库保存了您为服务指定的名称的产品。
在您的情况下,您可以将 when(repository.save(any()).thenReturn(product)
与您提前创建的产品一起使用。