Home > front end >  Proper usage of mockito ArgumentCaptor in Java?
Proper usage of mockito ArgumentCaptor in Java?

Time:01-05

I have looked at several usage examples of e.g. Using Mockito ArgumentCaptor, but I have confused about the proper usage for the following scenario:

Here is the method that I want to test:

@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);
}

My test method:

@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());
    }
}

Here are the questions (the numbers are also the comment numbers of test method):

1. I tested productRepository.save method using ArgumentCaptor and currencyService.getCurrencyByUuid(uuid) by returned value as it is not a repository call. Is that true?

2. I called the service method, but in some examples verify method is used. I am not sure if I need to use verify method for this example? Any idea?

3. I used the returned value for service and used captured value for repository. Is that a correct approach?

CodePudding user response:

To me, you do not need ArgumentCaptor here at all, you already mock your repositories and your method (create) already returns a ProductDTO. Testing this method, as such, is kind of trivial.

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());

As a disclaimer, I have not compiled the code above, but you should get the idea.

CodePudding user response:

Eugene is right, you don't need an ArgumentCaptor. To answer the question of when you need it: if you want to verify the value of a transatory object you don't have access to before or after your method.

For example, in

boolean createAndSave(String productName) {
  Product p = new Product(productName);
  return repository.save(p);
}

you don't have access to the created Product but want to verify it was created correctly. To do this, you can capture it:

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());

Now, you make sure that the repository was used to save a product with the name you gave the service.

In your case, you can just use when(repository.save(any()).thenReturn(product) with a product you created ahead of time.

CodePudding user response:

According to the documentation the recommended way to use ArgumentCaptor is not for stubbing, but for verification:

verify(productRepository).save(productCaptor.capture())

Argument captor does not look particularly useful for the provided create(...) implementation because if you're saving exactly the same object that you've passed to the method under the test, then passing actual argument to when(...) is enough for just mocking:

when(productRepository.save(product)).(...)

In my experience argument captors are used more in cases when arguments are not passed to the method under test explicitly(with your example imagine that productRepository#save is not saving the product passed to create(...), but rather some new instance that was created inside create(...) itself) or when the method under test changes some fields that are not included into the object's equals(...) method.

Verify should be used on mocked objects to assert that the actual method invocation happened specific amount of times with desired parameters.

Specifically answering your questions:

  1. Argument captor is not needed if the product passed to the productRepository#save is the same product that is passed to create(...), as you substitute when(productRepository.save(productCaptor.capture())) with just when(productRepository.save(product))
  2. Method under the test should be called without verify(...), verify is intended to be used with mocks to assert that specific mock methods were invoked
  3. Overall approach is correct
  •  Tags:  
  • Related