In Mockito, @Mock creates a fake object for a dependency, whereas @InjectMocks creates a real object of the class we test and passes the @Mock and @Spy fields into that object. The fake object, called a mock, runs none of the real code and returns only the values we set up in the test (setting up a return value is called stubbing).
We use the pair in unit tests of service classes, so we put @Mock on the helpers the class needs, such as a repository, and @InjectMocks on the class we test, such as a service. For example, a recipe service can be tested without the database behind its repository.
The following example tests RecipeService with a mock RecipeRepository.
@ExtendWith(MockitoExtension.class) // creates the annotated fields before each test
class RecipeServiceTest {
@Mock
RecipeRepository recipeRepository; // fake: returns what we stub
@InjectMocks
RecipeService recipeService; // real: new RecipeService(recipeRepository)
@Test
void returnsCookingTimeFromRepository() {
when(recipeRepository.findByName("pancakes"))
.thenReturn(Optional.of(new Recipe("pancakes", 20))); // 1. Stub the mock
int minutes = recipeService.cookingTime("pancakes"); // 2. Real code runs: 20
assertEquals(20, minutes); // 3. Passes
verify(recipeRepository).findByName("pancakes"); // 4. Passes
}
}
Notice that cookingTime() runs its real code and gets 20 from the stubbed mock. Mockito gives us three field annotations for a unit test, and each one creates a different kind of object.
| Annotation | Creates | Real method code runs? | Put it on |
|---|---|---|---|
| @Mock | A fake object. Methods we did not stub return null, 0, false or an empty collection | No | Dependencies (repositories, clients) |
| @Spy | A real object that Mockito wraps to record every call | Yes, unless stubbed | Real helper classes we want to check or partly stub |
| @InjectMocks | A real object of the class under test. Mockito passes the @Mock and @Spy fields into the object | Yes | The class under test |
Next, we look at how @InjectMocks picks between the constructor, a setter or a field, and why a failed injection gives no error. After that, we cover @Spy with @InjectMocks, MockitoAnnotations.openMocks(), final fields and records, the common NullPointerException mistakes, and how @Mock compares with Spring’s @MockitoBean.
1. What Are @Mock and @InjectMocks?
During unit testing, we test one class at a time, which is called the class under test, or the system under test (SUT). The SUT often needs other classes to do its work. For example, a service needs a repository that reads a database, but a unit test should not need that database, so we replace the repository with a mock.
A normal object runs the code inside its methods, whereas a mock runs none of the class’s code. Mockito creates the mock object and records every call to it, and each call returns either the stubbed value or a default value. @Mock creates the mock, and @InjectMocks creates the real SUT. Printing the class name of both fields shows the difference.
String mockClass = recipeRepository.getClass().getSimpleName(); // RecipeRepository$MockitoMock$WMIrPJ6X (suffix differs per run)
String serviceClass = recipeService.getClass().getSimpleName(); // RecipeService
RecipeRepository is an interface, so for the test Mockito generated a class that implements it. RecipeService is the plain class from src/main, so every line of cookingTime() runs for real, and only the values that the repository returns are fake.
1.1. Who Creates the Annotated Fields?
The annotations do nothing on their own, because some code must read them and fill the fields. In JUnit 5 and JUnit 6, that code is MockitoExtension, which we register with @ExtendWith(MockitoExtension.class). For each test method, the extension runs three steps.
- The extension creates a new mock for every @Mock field and a new spy for every @Spy field.
- It creates the @InjectMocks object and passes the mocks and spies into that object.
- After the test, it looks for stubs that the test never used, and fails the test when it finds one. Mockito calls this check strict stubs.
JUnit creates a new instance of the test class for each test method, so each test gets fresh mocks and a fresh SUT, and a stub from one test never affects the next test. Without the extension, the fields stay null (section 7.1). Mockito has one more field annotation, @Captor, among its Mockito annotations.
2. @Mock and @InjectMocks Example
The following example is a small recipe service built with Java 25, JUnit 6.1.3 and Mockito 5.24.0. The complete Maven project with 19 passing tests is on GitHub.
2.1. Maven Dependencies
The mockito-core artifact contains @Mock, @Spy and @InjectMocks, whereas mockito-junit-jupiter contains MockitoExtension. The JUnit BOM (bill of materials) keeps all JUnit modules on the same version.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.1.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.24.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.24.0</version>
<scope>test</scope>
</dependency>
</dependencies>
On Java 21 and later, we also load Mockito as a Java agent (a jar that the JVM loads to change classes). We load the agent in Surefire, the Maven plugin that runs the tests, and section 7.3 shows that setup and the warning it removes. A Spring Boot project gets all three libraries from spring-boot-starter-test instead.
2.2. The Class Under Test
RecipeService gets its RecipeRepository through the constructor, and the repository is an interface, so the test never needs a class that connects to a database.
public record Recipe(String name, int minutes) {}
public interface RecipeRepository {
Optional<Recipe> findByName(String name);
void save(Recipe recipe);
}
public class RecipeService {
private final RecipeRepository recipeRepository;
public RecipeService(RecipeRepository recipeRepository) {
this.recipeRepository = recipeRepository;
}
public int cookingTime(String name) {
return recipeRepository.findByName(name)
.map(Recipe::minutes)
.orElseThrow(() -> new RecipeNotFoundException(name)); // "Recipe soup not found"
}
public void addRecipe(String name, int minutes) {
if (minutes <= 0) {
throw new IllegalArgumentException("minutes must be positive");
}
recipeRepository.save(new Recipe(name.trim(), minutes));
}
}
2.3. Testing the Service With the Mock Repository
The test class from the top of the page has three more tests. The first one calls cookingTime(“soup”) without any stub. A method we did not stub returns a default value, which for Optional is Optional.empty(), so the not-found case needs no stub at all.
// 1. Unstubbed findByName("soup") returns Optional.empty()
RecipeNotFoundException e = assertThrows(RecipeNotFoundException.class,
() -> recipeService.cookingTime("soup"));
String message = e.getMessage(); // "Recipe soup not found"
// 2. Check the argument the service passed to the mock
recipeService.addRecipe(" lasagna ", 90);
verify(recipeRepository).save(new Recipe("lasagna", 90)); // passes, name was trimmed
// 3. Invalid input never reaches the repository
assertThrows(IllegalArgumentException.class, () -> recipeService.addRecipe("tea", 0));
verifyNoInteractions(recipeRepository); // passes
The method verify() checks that a method was called, and it works only on mocks and spies. Calling verify(recipeService) on the @InjectMocks object fails because that object is a real RecipeService, not a mock.
Argument passed to verify() is of type RecipeService and is not a mock!
Make sure you place the parenthesis correctly!
For more stubbing and verification methods, such as thenThrow() and times(), we can follow a JUnit Mockito example.
3. How @InjectMocks Chooses Constructor, Setter or Field Injection
@InjectMocks has three ways to pass the mocks in, and Mockito tries them in a fixed order, namely constructor injection, then property (setter) injection, then field injection. Which one Mockito uses depends on the constructors of the SUT.

The constructors and setters of the class under test decide the strategy.
| Class under test has | Strategy | Mocks matched by |
|---|---|---|
| A constructor with parameters | Constructor injection, the constructor with the most parameters | Type |
| A no-arg constructor and setXxx() methods | Setter (property) injection | Type, then field name and mock name |
| A no-arg constructor and plain fields | Field injection through reflection | Type, then field name and mock name |
3.1. Constructor Injection
When the SUT has a constructor with parameters, Mockito calls the constructor with the most parameters. TwoConstructorService has two constructors, one with one argument and one with two, and the test declares only a RecipeRepository mock.
public TwoConstructorService(RecipeRepository recipeRepository) { ... }
public TwoConstructorService(RecipeRepository recipeRepository, TimeFormatter timeFormatter) {
System.out.println("2-arg constructor called with " + recipeRepository + ", " + timeFormatter);
...
}
// In the test
@Mock RecipeRepository recipeRepository;
@InjectMocks TwoConstructorService service; // no TimeFormatter mock in the test
2-arg constructor called with recipeRepository, null
Mockito picked the two-argument constructor, even though the one-argument constructor would have worked, and because the test has no TimeFormatter mock, Mockito passed null for that parameter. For a parameter without a matching mock, Mockito passes null and reports nothing.
The test checks both facts, namely that service.recipeRepository() is the mock and service.timeFormatter() is null. The output shows recipeRepository because a mock prints its field name as its toString().
3.2. Setter Injection
When a class has a no-arg constructor and setter methods, Mockito passes the mock through the setter. SetterRecipeService prints a line from its setter, so we can see the call.
private RecipeRepository recipeRepository;
public void setRecipeRepository(RecipeRepository recipeRepository) {
System.out.println("setRecipeRepository() called with " + recipeRepository);
this.recipeRepository = recipeRepository;
}
setRecipeRepository() called with recipeRepository
3.3. Field Injection
When a class has no constructor with parameters and no setters, Mockito writes the mock into the field with reflection (the Java API that reads and changes fields at runtime), which works even when the field is private.
public class FieldRecipeService {
private RecipeRepository recipeRepository; // set by Mockito, no setter needed
...
}
when(recipeRepository.findByName("pancakes")).thenReturn(Optional.of(new Recipe("pancakes", 20)));
int minutes = service.cookingTime("pancakes"); // 20
When the test has two mocks of the same type, setter and field injection pick the mock whose field name equals the name of the SUT’s field. So we give the @Mock fields the same names as the SUT’s fields, which keeps the match clear.
3.4. @InjectMocks Skips a Field Without an Error
Once constructor injection succeeds, Mockito does not try setter or field injection for the other fields. MixedInjectionService shows the problem, because the service takes the repository in its constructor, whereas the formatter is only a field, with no constructor parameter and no setter.
public class MixedInjectionService {
private final RecipeRepository recipeRepository;
private TimeFormatter timeFormatter; // no constructor parameter, no setter
public MixedInjectionService(RecipeRepository recipeRepository) {
this.recipeRepository = recipeRepository;
}
...
}
The test declares a @Mock RecipeRepository and a @Spy TimeFormatter. Mockito calls the constructor with the repository and never sets timeFormatter, and no warning appears during setup, so the test fails later, inside the service.
[ERROR] com.howtodoinjava.recipes.MixedInjectionDemoTest.formatterFieldIsSkipped -- Time elapsed: 0.792 s <<< ERROR!
java.lang.NullPointerException: Cannot invoke "com.howtodoinjava.recipes.TimeFormatter.format(int)" because "this.timeFormatter" is null
at com.howtodoinjava.recipes.MixedInjectionService.menuLine(MixedInjectionService.java:19)
at com.howtodoinjava.recipes.MixedInjectionDemoTest.formatterFieldIsSkipped(MixedInjectionDemoTest.java:33)
The stack trace points at the service, not at the test setup, so the cause is hard to find. The fix belongs in the production class, where we put every required dependency in the constructor. Constructor injection also lets a test without Mockito call new MixedInjectionService(repository, formatter).
4. Using @Spy With @InjectMocks
A @Spy field holds a real object whose real methods run while Mockito records every call. @InjectMocks treats spies like mocks, so a spy also goes into the constructor. MenuService needs a repository, which we mock, and a TimeFormatter, which we spy on and which turns 90 into “1 h 30 min”.
@Mock
RecipeRepository recipeRepository; // fake
@Spy
TimeFormatter timeFormatter; // real object, calls recorded
@InjectMocks
MenuService menuService; // new MenuService(recipeRepository, timeFormatter)
when(recipeRepository.findByName("lasagna")).thenReturn(Optional.of(new Recipe("lasagna", 90)));
String line = menuService.menuLine("lasagna"); // "lasagna (1 h 30 min)", real format() ran
verify(timeFormatter).format(90); // passes
A @Spy field that we do not assign needs a no-arg constructor, because Mockito creates the object itself. To replace one method of a spy, we stub with doReturn(), because when(timeFormatter.format(90)) would first call the real method.
doReturn("long").when(timeFormatter).format(90);
String line = menuService.menuLine("lasagna"); // "lasagna (long)"
4.1. @Spy and @InjectMocks on the Same Field
Mockito allows both annotations on one field. Mockito creates the SUT with its mocks and wraps it in a spy, so we can stub or verify a method of the class under test itself.
@Mock
RecipeRepository recipeRepository;
@Spy
@InjectMocks
RecipeService recipeService;
doReturn(45).when(recipeService).cookingTime("curry");
int minutes = recipeService.cookingTime("curry"); // 45, the real method did not run
verify(recipeService).cookingTime("curry"); // passes
Stubbing the class under test is a sign that the class does too much, so we use this pattern only for legacy code, and the better fix is to split the class.
5. Using MockitoAnnotations.openMocks() Without the Extension
MockitoAnnotations.openMocks(this) fills the @Mock, @Spy and @InjectMocks fields of a test class without MockitoExtension. It returns an AutoCloseable, which we close after each test to release the mocks.
class OpenMocksTest { // no @ExtendWith
@Mock
RecipeRepository recipeRepository;
@InjectMocks
RecipeService recipeService;
AutoCloseable mocks;
@BeforeEach
void openMocks() {
mocks = MockitoAnnotations.openMocks(this);
}
@AfterEach
void closeMocks() throws Exception {
mocks.close();
}
}
The @BeforeEach method runs before every test, so each test still gets fresh objects. In practice, two differences from the extension matter.
- The method openMocks() does not check strict stubs. In our OpenMocksTest, a test with an unused findByName(“soup”) stub passes. With MockitoExtension, the same test fails with UnnecessaryStubbingException.
- The older MockitoAnnotations.initMocks() is deprecated since Mockito 3.4, and openMocks() replaces it.
MockitoExtension is the better default because of the strict stubs check. JUnit 4 tests used @RunWith(MockitoJUnitRunner.class) for the same purpose.
6. @InjectMocks With Final Fields and Records
Field and setter injection ignore final and static fields. Constructor injection has no such limit, because the constructor itself assigns the final fields, so the result depends on where the final field gets its value.
| Final field assigned in | Does the mock arrive? |
|---|---|
| A constructor parameter (this.repo = repo) | Yes, constructor injection |
| A field initializer or the no-arg constructor (= new InMemoryRecipeRepository()) | No, the field keeps the real object |
| A record component | Yes, through the canonical constructor (the constructor that takes all components) |
FinalFieldService creates its own repository in a final field, and the test stubs the mock anyway.
public class FinalFieldService {
private final RecipeRepository recipeRepository = new InMemoryRecipeRepository();
...
}
@Mock RecipeRepository recipeRepository;
@InjectMocks FinalFieldService service;
when(recipeRepository.findByName("pancakes")).thenReturn(Optional.of(new Recipe("pancakes", 20)));
int minutes = service.cookingTime("pancakes"); // RecipeNotFoundException, the empty in-memory repository was used
[ERROR] com.howtodoinjava.recipes.FinalFieldDemoTest.finalFieldKeepsRealRepository -- Time elapsed: 0.030 s <<< ERROR!
com.howtodoinjava.recipes.RecipeNotFoundException: Recipe pancakes not found
at com.howtodoinjava.recipes.FinalFieldService.lambda$cookingTime$0(FinalFieldService.java:14)
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
at com.howtodoinjava.recipes.FinalFieldService.cookingTime(FinalFieldService.java:14)
The stub was correct, but the service never called the mock because it used its own empty in-memory repository instead. A record has no such problem. RecipePrinter is a record with two components, so Mockito calls its canonical constructor and passes both mocks.
public record RecipePrinter(RecipeRepository recipeRepository, TimeFormatter timeFormatter) {
public String print(String name) { ... }
}
@Mock RecipeRepository recipeRepository;
@Mock TimeFormatter timeFormatter;
@InjectMocks RecipePrinter printer; // new RecipePrinter(recipeRepository, timeFormatter)
when(recipeRepository.findByName("lasagna")).thenReturn(Optional.of(new Recipe("lasagna", 90)));
when(timeFormatter.format(90)).thenReturn("90 minutes");
String line = printer.print("lasagna"); // "lasagna: 90 minutes"
7. Common @Mock and @InjectMocks Mistakes
Most mistakes with these annotations end in a NullPointerException. Since Java 14, the helpful NullPointerException message names the field that is null, and that field name tells us which mistake we made.
7.1. NullPointerException Because MockitoExtension Is Missing
Without @ExtendWith(MockitoExtension.class) or openMocks(), no code reads the annotations, so the @Mock field and the @InjectMocks field both stay null. MissingExtensionDemoTest is the test from the top of the page, without the extension line.
[ERROR] com.howtodoinjava.recipes.MissingExtensionDemoTest.fieldsAreNull -- Time elapsed: 0.013 s <<< ERROR!
java.lang.NullPointerException: Cannot invoke "com.howtodoinjava.recipes.RecipeRepository.findByName(String)" because "this.recipeRepository" is null
at com.howtodoinjava.recipes.MissingExtensionDemoTest.fieldsAreNull(MissingExtensionDemoTest.java:24)
The failing line is when(recipeRepository.findByName(…)) in the test itself. When the stack trace ends in the test class and names a @Mock field, the extension is missing. To fix the test, we add @ExtendWith(MockitoExtension.class) to the class or call openMocks() in @BeforeEach (section 5).
7.2. NullPointerException Inside the Class Under Test
When the stack trace ends inside the SUT and names one of its fields, Mockito did read the annotations, but @InjectMocks could not set that field. The causes come from section 3 and section 6.
| Symptom | Cause | Fix |
|---|---|---|
| Field missing from the constructor is null | Constructor injection ran, so other fields were skipped (3.4) | Add the dependency to the constructor |
| Constructor parameter is null | No @Mock or @Spy of that type in the test (3.1) | Declare the missing mock |
| Stub has no effect, real object used | Final field set inside the class (6) | Pass the dependency through the constructor |
7.3. Mockito Self-Attaching Warning on Java 21 and Later
Mockito 5 changes classes while the tests run, and for that it uses a Java agent. Without extra setup, Mockito attaches the agent to the running test JVM by itself, and Java 21 and later print a warning about that late attach. On Java 25, our mvn test run printed five warning lines.
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
WARNING: A Java agent has been loaded dynamically (/root/.m2/repository/net/bytebuddy/byte-buddy-agent/1.17.7/byte-buddy-agent-1.17.7.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
The tests still pass, but a future JDK will block an agent that loads this late. The fix has two parts. First, the properties goal of the Maven Dependency Plugin stores the path of each dependency jar in a property. Second, Surefire passes the Mockito jar to the JVM as -javaagent.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.11.0</version>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<argLine>-javaagent:@{org.mockito:mockito-core:jar}</argLine>
</configuration>
</plugin>
@{…} is Surefire’s late property replacement, so Surefire reads the property when the tests start, after the properties goal has set it. With this setup, the five warning lines no longer appear. The related Could not initialize plugin message is a different MockMaker error.
8. @Mock vs @MockitoBean in Spring Boot
@Mock and @InjectMocks work without Spring, because Mockito creates both objects, and no Spring application context (the container where Spring keeps the objects, or beans, it creates) starts. Spring’s @MockitoBean is a different tool. _@MockitoBean_ replaces a bean in the Spring application context with a mock, so Spring, not Mockito, injects the mock into the other beans.
| @Mock + @InjectMocks | @MockitoBean + @Autowired | |
|---|---|---|
| Comes from | Mockito | Spring Framework 6.2+ (org.springframework.test.context.bean.override.mockito) |
| Needs | MockitoExtension | @SpringBootTest, @WebMvcTest or another Spring test |
| Spring context | None, runs in milliseconds | Started, takes seconds |
| Strict stubs check | Yes | No |
@MockitoBean replaces Spring Boot’s @MockBean, which Spring Boot 4 removed. In a Spring Boot service, we test business logic with @Mock and @InjectMocks, and we use @MockitoBean only when the test checks something Spring does, such as wiring or web endpoints. We can compare both setups and their run times in a Spring Boot Mockito JUnit example.
9. @Mock and @InjectMocks FAQs
9.1. Can @InjectMocks Inject Mocks Into Another Mock?
No. @InjectMocks creates a real object, not a mock, so Mockito rejects both annotations on one field before the test starts.
org.mockito.exceptions.base.MockitoException:
This combination of annotations is not permitted on a single field:
@Mock and @InjectMocks
For a real object with some stubbed methods, we combine @Spy and @InjectMocks (section 4.1).
9.2. Do We Need @InjectMocks at All?
No. We can create the SUT ourselves in a @BeforeEach method, with recipeService = new RecipeService(recipeRepository). The manual call has one advantage, because when someone adds a constructor parameter, the test fails at compile time, whereas @InjectMocks passes null and reports no error. So many teams prefer the manual constructor call, even though @InjectMocks saves one line per dependency.
9.3. What Is the Difference Between @Mock and @Spy?
A @Mock runs no real code, so a method we did not stub returns a default value. A @Spy runs the real code unless we stub a method. Both record calls for verify(), and @InjectMocks passes both into the SUT.
9.4. Are @Mock Fields Shared Between Test Methods?
No. With MockitoExtension, or openMocks() in @BeforeEach, every test method gets new mocks and a new @InjectMocks object, so the stubs and recorded calls start empty in each test.
10. Conclusion
@Mock creates a fake dependency and @Spy wraps a real object. @InjectMocks creates the real class under test and passes those objects in. MockitoExtension or openMocks() must read the annotations, or every field stays null.
@InjectMocks uses the biggest constructor when the class has one, and then stops. Mockito uses setter and field injection only for classes with a no-arg constructor, and both ignore final and static fields. Mockito never reports a field that it could not set, so constructor injection in the production code avoids almost all of these surprises. On Java 21 or later, we also load Mockito as a Java agent in Surefire.
11. References
- Mockito @InjectMocks Javadoc
- Mockito @Spy Javadoc
- MockitoAnnotations Javadoc
- MockitoExtension Javadoc
- Mockito Javadoc: Mockito as a Java agent
- JUnit 6.1.3 User Guide
Happy Learning !!
hi Lokesh, what if we want to mock a method defined in class annoted with @InjeckMock class
Use Mockito.spy(). The spied class uses the real methods for all calls except for those that you’ve specifically mocked.
This example doesn’t seem correct. These 2 save() methods require a String parameter and don’t return a boolean:
when(dependentClassOne.save()).thenReturn(true);
when(dependentClassTwo.save()).thenReturn(true);
“public boolean save(String fileName)” is the method declaration. What are you referring to?
public boolean save(String fileName) is a method in Main.class.
You are trying to mock public void save(String fileName) in DatabaseDAO and NetworkDAO.
Oh. Thanks for noticing and reporting. Its updated now.
It is Injectmock not initmock.
Not when you use the MockitoAnnotations.initMocks method.