DEV Community

Charl Gerber
Charl Gerber

Posted on AI-assisted

Why Exact Arguments Matter in Mockito Tests

Mockito stubbing and verification with exact arguments are powerful testing techniques. Stubbing defines the expected behaviour of mocked objects, while verification checks how they are used. Both can help detect bugs early and make test code easier to understand.

However, Mockito also provides general argument matchers that, while convenient, can make tests less effective when the specific arguments of an interaction matter. Mockito's strict-stubbing features can help detect some unnecessary or mismatched stubs, but they are not a substitute for verifying that the correct interactions occur with the expected arguments.

Let's look at this simple function that transfers money from one account to the other:

public void transferMoney(String fromAccount, String toAccount, double amount) {
    moneyService.debit(toAccount, amount);
    moneyService.credit(fromAccount, amount);
}
Enter fullscreen mode Exit fullscreen mode

A visual inspection reveals the obvious bug in the code: the implementation debits toAccount and credits fromAccount, so the accounts are reversed. But it was never detected because this was the unit test written for this function:

@Test
void shouldDebitAndCredit_whenTransferMoney_givenValidAccounts() {
    transferMoney("IBAN1", "IBAN2", 10.00);
    verify(moneyService).debit(anyString(), 10.00);
    verify(moneyService).credit(anyString(), 10.00);
}
Enter fullscreen mode Exit fullscreen mode

Had the test been written with exact argument verification, the bug would have been discovered straight away with this test failing:

@Test
void shouldDebitAndCredit_whenTransferMoney_givenValidAccounts() {
    transferMoney("IBAN1", "IBAN2", 10.00);
    verify(moneyService).debit("IBAN1", 10.00);
    verify(moneyService).credit("IBAN2", 10.00);
}
Enter fullscreen mode Exit fullscreen mode

This is especially important in test-driven development (TDD), where tests are often written before the implementation. Without precise argument verification, bugs like this can go undetected.

Let's look at another example:

public Optional<Manager> getByEmployee(String employeeId) {
    return employeeRepository.findById(employeeId).map(Employee::getManager);
 }
Enter fullscreen mode Exit fullscreen mode

And here is the test that was written for it:

@Test
void shouldReturnManager_whenGetManager_givenEmployeeWithManager() {
    when(employeeRepository.findById(anyString())).thenReturn(Optional.of(employee));
    final var maybeManager = managerService.getByEmployee("UserWithId");
    assertThat(maybeManager).contains(expectedManager);
    }
Enter fullscreen mode Exit fullscreen mode

At first glance, it’s hard to fault this test. But suppose the specification changes to convert all user IDs to lowercase before being passed to the database repository. A developer updates the implementation but accidentally converts the IDs to uppercase instead. The existing unit test fails to detect this mistake and the bug goes undetected. The correct unit test should therefore be:

@Test
void shouldReturnManager_whenGetManager_givenEmployeeWithManagerInCamelCase() {
    when(employeeRepository.findById("userwithid")).thenReturn(Optional.of(employee));
    final var maybeManager = managerService.getByEmployee("UserWithId");
    assertThat(maybeManager).contains(expectedManager);
    }
Enter fullscreen mode Exit fullscreen mode

Of course, general argument matchers such as any() and anyString() are useful and sometimes even the best choice. However, when the specific arguments matter to the behaviour being tested, prefer exact arguments in your Mockito stubs and verifications. This makes tests more precise, expectations easier to understand, and bugs less likely to go undetected.

Top comments (0)