Why is my class not calling my mocked methods in unit test?

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
  }
}

References

Leave a Comment