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.exceptions.misusing.InjectMocksException;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class AutoMockitoExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
var testInstance = context.getRequiredTestInstance();
var 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);
if (injectMocksObject == null) {
throw new IllegalStateException("""
@InjectMocks field is null.
You may need to add MockitoExtension.class to the beginning of the @ExtendWith extensions list""");
}
// 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({MockitoExtension.class, AutoMockitoExtension.class})
class InviteServiceTests {
@Mock
private EmailSendingService emailSendingService;
@InjectMocks
private InviteService inviteService;
@Test
void testSomething() {
inviteService.inviteUser("[email protected]");
verify(emailSendingService).sendEmail("[email protected]");
}
}
In this example, I haven’t accounted for all the possible details and pitfalls that might need to be considered, but it works.
UserServiceisnullinInviteService, so I assume the NPE is coming from withinInviteService, which presumably interacts withUserService. Guessing, either you need some expectations onUserService(which will remove the unused field warning) or there might be some concerns that need separating.UserServiceto be set to null, I want it to be set to a default mock, i.e. the same object that is returned byMockito.mock(UserService.class)null. Would be nice to add a new field to@injectMocksthat when set totruewould do what @Georgii Lvov suggests.