Mockito @Mock vs @InjectMocks: Difference With Examples

@Mock creates a fake dependency. @InjectMocks creates the real class under test and passes the mocks into it. Learn how @InjectMocks picks the constructor, a setter or a field, and why a missed field gives no error. Also covers @Spy, openMocks(), final fields, records and NullPointerException fixes.

Mockito

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.

AnnotationCreatesReal method code runs?Put it on
@MockA fake object. Methods we did not stub return null, 0, false or an empty collectionNoDependencies (repositories, clients)
@SpyA real object that Mockito wraps to record every callYes, unless stubbedReal helper classes we want to check or partly stub
@InjectMocksA real object of the class under test. Mockito passes the @Mock and @Spy fields into the objectYesThe 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.

  1. The extension creates a new mock for every @Mock field and a new spy for every @Spy field.
  2. It creates the @InjectMocks object and passes the mocks and spies into that object.
  3. 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.

Flow chart of @InjectMocks. If the class has a constructor with parameters, Mockito uses constructor injection. Mockito picks the constructor with the most parameters and fills each parameter with a @Mock or @Spy field of that type. Mockito passes null when no mock matches. Then Mockito stops, so fields that are not constructor parameters stay null. Otherwise Mockito creates the object with the no-arg constructor. Then Mockito tries setter injection, then field injection, and skips final and static fields. A dependency that no strategy can set stays as it is, and Mockito reports no failure
@InjectMocks uses the constructor when it can, and then stops. Mockito uses setters and fields only for a class with a no-arg constructor.

The constructors and setters of the class under test decide the strategy.

Class under test hasStrategyMocks matched by
A constructor with parametersConstructor injection, the constructor with the most parametersType
A no-arg constructor and setXxx() methodsSetter (property) injectionType, then field name and mock name
A no-arg constructor and plain fieldsField injection through reflectionType, 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 inDoes 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 componentYes, 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.

SymptomCauseFix
Field missing from the constructor is nullConstructor injection ran, so other fields were skipped (3.4)Add the dependency to the constructor
Constructor parameter is nullNo @Mock or @Spy of that type in the test (3.1)Declare the missing mock
Stub has no effect, real object usedFinal 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 fromMockitoSpring Framework 6.2+ (org.springframework.test.context.bean.override.mockito)
NeedsMockitoExtension@SpringBootTest, @WebMvcTest or another Spring test
Spring contextNone, runs in millisecondsStarted, takes seconds
Strict stubs checkYesNo

@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

Happy Learning !!

Source Code on Github

Leave a Comment

  1. 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);

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.