Preface:
This question and answer are intended as a canonical answer to the majority of questions that arise due to misuse of Mockito or misunderstanding how Mockito works and interacts with unit tests written in the Java language.
I have implemented a class which should be unit tested. Note that the code shown here is only a dummy implementation and Random
is for illustrative purposes. Real code would use a real dependency, such as another service or repository.
public class MyClass {
public String doWork() {
final Random random = new Random(); // the `Random` class will be mocked in the test
return Integer.toString(random.nextInt());
}
}
I want to use Mockito for mocking other classes and have written a really simple JUnit test. However, my class is not using the mock in the test:
public class MyTest {
@Test
public void test() {
Mockito.mock(Random.class);
final MyClass obj = new MyClass();
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
// this fails, because the `Random` mock is not used :(
}
}
Even running the test with the MockitoJUnitRunner
(JUnit 4) or extending with MockitoExtension
(JUnit 5) and annotating with @Mock
does not help; the real implementation is still used:
@ExtendWith(MockitoExtension.class) // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTest {
@Mock
private Random random;
@Test
public void test() {
final MyClass obj = new MyClass();
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
// `Random` mock is still not used :((
}
}
Why is the mocked class not used, even though the Mockito methods are called before my class is tested or the test is executed with the Mockito extension/runner?
Other variants of this question include, but are not limited to:
thenReturn
not honored / Mockito thenAnswer
not honored@InjectMocks
not working@Mock
not workingMockito.mock
not workingTLDR: 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.
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.
»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:
Below you will find sample implementations for each of the options (with and without Mockito runner/extension):
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
}
}
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
}
}
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
}
}
@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 withMockito.mock
andMockito.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:
@Mock
-annotated fields get assigned a new mock object.@InjectMocks
-annotated field gets injected references to the mock object(s) from step 1.Mockito.mock
). The original reference is lost and no longer available in the test class.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
}
}
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:
MyTestAnnotated
is created by the testing framework (e.g. new MyTestAnnotated()
).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)
.@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
}
}