In my Spring Boot app, I have some tests that look like this
@ExtendWith(MockitoExtension.class)
class InviteServiceTests {
@Mock
private UserService userService;
@Mock
private EmailSendingService emailSendingService;
@InjectMocks
private InviteService inviteService;
@Test
void testSomething() {
inviteService.inviteUser("user@example.com");
verify(emailSendingService).sendEmail("user@example.com");
}
}
I have to declare userService
because it's a dependency of inviteService
that is called during inviteService.inviteUser
. However I don't need to mock or verify any userService
methods, so IntelliJ marks this as an unused field.
I'm aware I could tell IntelliJ to ignore any used fields annotated with @Mock
, but what I'd prefer is to not have to declare this field at all. In other words, when a dependency of InviteService
is found that does not have a corresponding @Mock
field, Mockito will automatically create a default mock for the dependency.
Currently, if the userService
field is removed, the dependency is set to null, which causes a NullPointerException
when the test runs.
To achieve this behavior, it seems that there is no out-of-the-box solution in Mockito. However, you can write your own custom extension that inspects your test class, finds a field annotated with @InjectMocks
, examines its fields, and if any of them are null, instantiates them with mock objects. It could look something like this:
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.exceptions.misusing.InjectMocksException;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class AutoMockExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
var testInstance = context.getRequiredTestInstance();
/*
Initialize any @Mock field of the test instance to a mock object. This effectively does this same job as
@ExtendWith(MockitoExtension.class), which is why it is unnecessary to use MockitoExtension and
AutoMockitoExtension together
*/
MockitoAnnotations.openMocks(testInstance);
Field injectMocksField = Arrays.stream(testInstance.getClass().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(InjectMocks.class))
.findFirst()
.orElseThrow(() -> new IllegalStateException("@InjectMocks field not found"));
injectMocksField.setAccessible(true);
var injectMocksObject = injectMocksField.get(testInstance);
// Assign a mock object to any null instance field of the @InjectMocks object
ReflectionUtils.doWithFields(injectMocksObject.getClass(),
field -> field.set(injectMocksObject, Mockito.mock(field.getType())),
field -> {
var isInstanceField = !Modifier.isStatic(field.getModifiers());
field.setAccessible(true);
try {
return isInstanceField && field.get(injectMocksObject) == null;
} catch (IllegalAccessException e) {
throw new InjectMocksException("Failed to access field: " + field, e);
}
});
}
}
And then test:
@ExtendWith(AutoMockExtension.class)
// or @ExtendWith({MockitoExtension.class, AutoMockExtension.class})
class InviteServiceTests {
@Mock
private EmailSendingService emailSendingService;
@InjectMocks
private InviteService inviteService;
@Test
void testSomething() {
inviteService.inviteUser("user@example.com");
verify(emailSendingService).sendEmail("user@example.com");
}
}
In this example, I haven’t accounted for all the possible details and pitfalls that might need to be considered, but it works.