Search code examples
javaunit-testingtestingjunitjunit5

ArgumentCaptor usage in Unit Tests


I am trying to create a Unit Test for the following service method:

public CompanyDTO update(CompanyRequest companyRequest, UUID uuid) {

    final Company company = companyRepository.findByUuid(uuid)
            .orElseThrow(() -> new EntityNotFoundException("Not found"));            
    
    company.setName(companyRequest.getName());

    final Company saved = companyRepository.save(company);
    return new CompanyDTO(saved);
}

I created the following Unit Test:

@InjectMocks
private CompanyServiceImpl companyService;

@Mock
private CompanyRepository companyRepository;

@Captor
ArgumentCaptor<Company> captor;



@Test
public void testUpdate() {
    final Company company = new Company();
    company.setName("Company Name");

    final UUID uuid = UUID.randomUUID();
    final CompanyRequest request = new CompanyRequest();
    request.setName("Updated Company Name");
  
    when(companyRepository.findByUuid(uuid))
        .thenReturn(Optional.ofNullable(company));        
    when(companyRepository.save(company)).thenReturn(company);

    CompanyDTO result = companyService.update(request, uuid);

    /* here we get the "company" parameter value that we send to save method 
    in the service. However, the name value of this paremeter is already 
    changed before passing to save method. So, how can I check if the old 
    and updated name value? */
    Mockito.verify(companyRepository).save(captor.capture());

    Company savedCompany = captor.getValue();

    assertEquals(request.getName(), savedCompany.getName());
}

As far as I know, we use ArgumentCaptor to catch the value we pass to a method. In this example, I need to catch the value at correct time and compare the updated value of name sent to the update method and the returned value of name property after update. However, I cannot find how to test it properly and add necessary comment to my test method. So, how should I use ArgumentCaptor to verify my update method updates the company with the given value ("Updated Company Name").


Solution

  • Here's your scenario for an ArgumentCaptor.

    Code under test:

    public void createProduct(String id) {
      Product product = new Product(id);
      repository.save(product);
    }
    

    Note that a) we create an object within the tested code, b) we want to verify something specific with that object, and c) we don't have access to that object after the call. Those are pretty much the exact circumstances where you need a captor.

    You can test it like this:

    void testCreate() {
      final String id = "id";
      ArgumentCaptor<Product> captor = ArgumentCaptor.for(Product.class);
    
      sut.createProduct(id);
    
      // verify a product was saved and capture it
      verify(repository).save(captor.capture());
      final Product created = captor.getValue();
    
      // verify that the saved product which was captured was created correctly
      assertThat(created.getId(), is(id));
    }