Why are my mocked methods not called when executing a unit test?

TLDR: Two or more distinct instances of your mock exist. Your test is using one instance and your class under test is using another instance. Or you are not using mocks at all in your class because you new up objects inside the class.


Problem Overview (Classes vs Instances)

Mocks are instances (that’s why they are also called “mock objects”). Calling Mockito.mock on a class will return a mock object for this class. It must be assigned to a variable which can then be passed to the relevant methods or injected as dependency into other classes. It does not modify the class itself! Think about it: if that were true, then all instances of the class would somehow, magically, be converted to mocks. That would make it impossible to mock classes of which multiple instances are used or classes from the JDK, such as List or Map (which shouldn’t be mocked in the first place, but that’s a different story).

The same holds true for @Mock annotation with the Mockito extension/runner: a new instance of a mock object is created, which is then assigned to the field (or parameter) annotated with @Mock. This mock object still needs to be passed to the correct methods or injected as dependency.

Another way to avoid this confusion: new in Java will always allocate memory for an object and will initialize this new instance of the real class. It is impossible to override the behavior of new. Not even clever frameworks such as Mockito can do it.


Solution

»But how can I mock my class?« you will ask. Change the design of your classes to be testable! Every time you decide to use new, you commit yourself to an instance of this exact type. Multiple options exist, depending on your concrete use case and requirements, including but not limited to:

  1. If you can change the signature/interface of the method, pass the (mock) instance as method parameter. This requires the instance to be available in all call sites, which might not always be feasible.
  2. If you cannot change the signature of the method, inject the dependency in your constructor and store it in a field to be later used by methods.
  3. Sometimes, the instance must only be created when the method is called and not prior to it. In that case, you can introduce another level of indirection and use something known as the abstract factory pattern. The factory object will then create and return the instance of your dependency. Multiple implementations of the factory can exist: one which returns the real dependency and another one which returns a test double, such as a mock.

Below you will find sample implementations for each of the options (with and without Mockito runner/extension):

Changing Method Signature

public class MyClass {
  public String doWork(final Random random) {
    return Integer.toString(random.nextInt());
  }
}

public class MyTest {
  @Test
  public void test() {
    final Random mockedRandom = Mockito.mock(Random.class);
    final MyClass obj = new MyClass();
    Assertions.assertEquals("0", obj.doWork(mockedRandom)); // JUnit 5
    // Assert.assertEquals("0", obj.doWork(mockedRandom));  // JUnit 4
  }
}

@ExtendWith(MockitoExtension.class)   // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
  @Mock
  private Random random;

  @Test
  public void test() {
    final MyClass obj = new MyClass();
    Assertions.assertEquals("0", obj.doWork(random)); // JUnit 5
    // Assert.assertEquals("0", obj.doWork(random));  // JUnit 4
  }
}

Constructor Dependency Injection

public class MyClass {
  private final Random random;

  public MyClass(final Random random) {
    this.random = random;
  }

  // optional: make it easy to create "production" instances (although I wouldn't recommend this)
  public MyClass() {
    this(new Random());
  }

  public String doWork() {
    return Integer.toString(random.nextInt());
  }
}

public class MyTest {
  @Test
  public void test() {
    final Random mockedRandom = Mockito.mock(Random.class);
    final MyClass obj = new MyClass(mockedRandom);
    // or just obj = new MyClass(Mockito.mock(Random.class));
    Assertions.assertEquals("0", obj.doWork()); // JUnit 5
    // Assert.assertEquals("0", obj.doWork());  // JUnit 4
  }
}

@ExtendWith(MockitoExtension.class)   // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
  @Mock
  private Random random;

  @Test
  public void test() {
    final MyClass obj = new MyClass(random);
    Assertions.assertEquals("0", obj.doWork()); // JUnit 5
    // Assert.assertEquals("0", obj.doWork());  // JUnit 4
  }
}

Delayed Construction via Factory

Depending on the number of constructor arguments of your dependency and need for expressive code, one could use existing interfaces from the JDK (Supplier, Function, BiFunction) or introduce a custom factory interface (annotated with @FunctionInterface if it only has a single method).

The following code will opt for a custom interface, but would work just fine with Supplier<Random>.

@FunctionalInterface
public interface RandomFactory {
  Random newRandom();
}

public class MyClass {
  private final RandomFactory randomFactory;

  public MyClass(final RandomFactory randomFactory) {
    this.randomFactory = randomFactory;
  }

  // optional: make it easy to create "production" instances (again: I wouldn't recommend this)
  public MyClass() {
    this(Random::new);
  }

  public String doWork() {
    return Integer.toString(randomFactory.newRandom().nextInt());
  }
}

public class MyTest {
  @Test
  public void test() {
    final RandomFactory randomFactory = () -> Mockito.mock(Random.class);
    final MyClass obj = new MyClass(randomFactory);
    Assertions.assertEquals("0", obj.doWork()); // JUnit 5
    // Assert.assertEquals("0", obj.doWork());  // JUnit 4
  }
}

@ExtendWith(MockitoExtension.class)   // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
  @Mock
  private RandomFactory randomFactory;

  @Test
  public void test() {
    // this is really awkward; it is usually simpler to use a lambda and create the mock manually
    Mockito.when(randomFactory.newRandom()).thenAnswer(a -> Mockito.mock(Random.class));
    final MyClass obj = new MyClass(randomFactory);
    Assertions.assertEquals("0", obj.doWork()); // JUnit 5
    // Assert.assertEquals("0", obj.doWork());  // JUnit 4
  }
}

Corollary: (Mis-)using @InjectMocks

But I’m using @InjectMocks and verified with the debugger that I have mocks inside my class under test. Yet, the mock methods I set up with Mockito.mock and Mockito.when are never called! (In other words: “I get an NPE”, “my collections are empty”, “default values are returned”, etc.)

— a confused developer, ca. 2022

Expressed in code, the above quote would look something like this:

@ExtendWith(MockitoExtension.class)   // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
  @Mock
  private Random random;

  @InjectMocks
  private MyClass obj;

  @Test
  public void test() {
    random = Mockito.mock(Random.class);
    Mockito.when(random.nextInt()).thenReturn(42);
    Assertions.assertEquals("42", obj.doWork()); // JUnit 5
    // Assert.assertEquals("42", obj.doWork());  // JUnit 4
  }
}

The problem with the code above is the first line in the test() method: it creates and assigns a new mock instance to the field, effectively overwriting the existing value. But @InjectMocks injects the original value into the class under test (obj). The instance created with Mockito.mock only exists in the test, not in the classes under test.

The order of operations here is:

  1. All @Mock-annotated fields get assigned a new mock object.
  2. The @InjectMocks-annotated field gets injected references to the mock object(s) from step 1.
  3. The reference in the test class is overwritten with a different reference to the new mock object (created via Mockito.mock). The original reference is lost and no longer available in the test class.
  4. The class under test (obj) still holds a reference to the initial mock instance and uses that. The test only has a reference to the new mock instance.

This basically boils down to Is Java “pass-by-reference” or “pass-by-value”?.

You can verify this with a debugger. Set a breakpoint and then compare the object addresses/ids of the mock fields in the test class and in the class under test. You will notice that those are two different, unrelated object instance.

The solution? Don’t overwrite the reference, but set up the mock instance created via the annotation. Simply get rid of the re-assignment with Mockito.mock:

@ExtendWith(MockitoExtension.class)   // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
  @Mock
  private Random random;

  @InjectMocks
  private MyClass obj;

  @Test
  public void test() {
    // this.random must not be re-assigned!
    Mockito.when(random.nextInt()).thenReturn(42);
    Assertions.assertEquals("42", obj.doWork()); // JUnit 5
    // Assert.assertEquals("42", obj.doWork());  // JUnit 4
  }
}

Corollary: Object life cycles and magic framework annotations

I followed your advice and use dependency injection to manually pass the mock into my service. It’s still not working and my test fails with null pointer exceptions, sometimes even before a single test method actually runs. You lied, bruh!

— another confused developer, late 2022

The code would likely look something like:

@ExtendWith(MockitoExtension.class)   // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
  @Mock
  private Random random;

  private final MyClass obj = new MyClass(random);

  @Test
  public void test() {
    Mockito.when(random.nextInt()).thenReturn(42);
    Assertions.assertEquals("42", obj.doWork()); // JUnit 5
    // Assert.assertEquals("42", obj.doWork());  // JUnit 4
  }
}

This is very similar to the first corollary and boils down to object life cycles and references vs values. The steps happening in the code above are as follows:

  1. A new instance of MyTestAnnotated is created by the testing framework (e.g. new MyTestAnnotated()).
  2. All constructors and field initializers are executed. There are no constructors here, but a field initializer: private MyClass obj = new MyClass(random);. At this point in time, the random field still has its default value null → the obj field is assigned new MyClass(null).
  3. All @Mock-annotated fields get assigned a new mock object. This does not update the value in MyService obj, because it got passed null initially, not a reference to the mock.

Depending on your MyService implementation, this might already fail when creating an instance of the test class (MyService might perform parameter validation of its dependencies in the constructor); or it might only fail when executing a test method (because the dependency is null).

The solution? Familiarize yourself with object life cycles, field initializer order, and the point in time when mock frameworks can/will inject their mocks and update references (and which references are updated). Try to avoid mixing “magic” framework annotations with manual setup. Either create everything manually (mocks, service), or move the initialization to the methods annotated with @Before (JUnit 4) or @BeforeEach (JUnit 5).

@ExtendWith(MockitoExtension.class)   // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
  @Mock
  private Random random;

  private MyClass obj;

  @BeforeEach // JUnit 5
  // @Before  // JUnit 4
  public void setup() {
    obj = new MyClass(random);
  }

  @Test
  public void test() {
    Mockito.when(random.nextInt()).thenReturn(42);
    Assertions.assertEquals("42", obj.doWork()); // JUnit 5
    // Assert.assertEquals("42", obj.doWork());  // JUnit 4
  }
}

Alternatively, set up everything manually without annotations which require a custom runner/extension:

public class MyTest {
  private Random random;
  private MyClass obj;

  @BeforeEach // JUnit 5
  // @Before  // JUnit 4
  public void setup() {
    random = Mockito.mock(random);
    obj = new MyClass(random);
  }

  @Test
  public void test() {
    Mockito.when(random.nextInt()).thenReturn(42);
    Assertions.assertEquals("42", obj.doWork()); // JUnit 5
    // Assert.assertEquals("42", obj.doWork());  // JUnit 4
  }
}

References

Leave a Comment