Home > Software design >  ArgumentCaptor usage in Unit Tests
ArgumentCaptor usage in Unit Tests

Time:11-11

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").

CodePudding user response:

Here's how I would write this test case, with explanations as code comments.

@Test
public void testUpdate() {
    // we make this a mock so we can analyze it later
    final Company company = mock(Company.class);

    // no need to have the same String literal twice
    final String updatedName = "Updated Company Name";

    // we make this a mock because only that one method is used
    // if you create an actual object, you'll need to adjust this test 
    // when the constructor changes later
    final CompanyRequest request = mock(CompanyRequest.class);
    when(request.getName()).thenReturn(updatedName);
  
    // we don't really care about the actual uuid, so any() will do here
    when(companyRepository.findByUuid(any()))
        .thenReturn(Optional.ofNullable(company));
    when(companyRepository.save(company)).thenReturn(company);

    // setup complete, let's go
    CompanyDTO result = companyService.update(request, UUID.randomUUID());

    // we want to make sure the name change has been applied
    Mockito.verify(company.setName(udpatedName);
    // make sure it has been saved as well
    Mockito.verify(companyRepository).save(company);

    // verify that the returned dto actually contains the company we've been working on
    Company savedCompany = result.getCompany();
    assertSame(savedCompany, company);
}

Note that this does not answer your actual question about how to use an argument captor, but that's because that was an X-Y problem.

CodePudding user response:

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));
}
  • Related