This rule raises an issue when test mock stubbing is syntactically incomplete.
In Mockito, this means: a when() call must be followed by thenReturn(), thenThrow(),
thenAnswer(), or thenCallRealMethod(); and a doReturn(), doThrow(), doAnswer(),
doNothing(), or doCallRealMethod() call must be followed by .when(mock).method().
Test double frameworks allow you to configure behavior through chained API calls. These chains must be syntactically complete to take effect.
When you write the first part of a stub configuration (specifying which method call to intercept) without completing the chain with a behavior specification (what value to return or action to take), the configuration is discarded. The method continues to return its default value (null for objects, 0 for numbers, false for booleans).
Similarly, when you write the behavior specification first (what to return) without completing it with the method specification (which method to stub), the configured behavior is never attached to any method. It is silently dropped.
Both patterns compile and run without errors in lenient testing frameworks. This makes them hard to spot during code review or testing. The test may pass because it never actually exercises the intended stub, or it may fail with a confusing error message that doesn’t point to the incomplete stubbing.
The incomplete stub creates a gap between what you intended to test and what the test actually verifies. This undermines the reliability of your test suite.
Incomplete mock object stubs can cause:
Complete the when() chain by adding a behavior method such as thenReturn(), thenThrow(),
thenAnswer(), or thenCallRealMethod().
@Test
void testFindUser() {
UserRepository mock = mock(UserRepository.class);
when(mock.findUser(42)); // Noncompliant
User user = service.getUser(42);
assertNotNull(user); // Will fail - findUser returns null
}
@Test
void testFindUser() {
UserRepository mock = mock(UserRepository.class);
when(mock.findUser(42)).thenReturn(new User("Alice"));
User user = service.getUser(42);
assertNotNull(user); // Now passes correctly
}
Complete the do*() chain by adding .when(mock).method() to specify which method should have the configured behavior.
@Test
void testDeleteUser() {
UserRepository mock = mock(UserRepository.class);
doThrow(new RuntimeException()); // Noncompliant
assertThrows(RuntimeException.class, () -> {
service.deleteUser(42);
}); // Will fail - no exception is thrown
}
@Test
void testDeleteUser() {
UserRepository mock = mock(UserRepository.class);
doThrow(new RuntimeException()).when(mock).deleteUser(42);
assertThrows(RuntimeException.class, () -> {
service.deleteUser(42);
}); // Now passes correctly
}