Search code examples
javaunit-testingdependency-injectionmockitoguice

Mockito replaced already mocked objects with new ones after calling openMocks for InjectMocks annotated object


I have this weird situation that Mockito is replacing already mocked objects with new ones after calling openMocks() for @InjectMocks annotated object.

Here is my setup:

@RunWith(MockitoJUnitRunner.class) public class MyTest {
    @Mock ClassA mockClassA;
    @Mock ClassB mockClassB;
    @InjectMocks ClassToTest classToTest;
    AutoCloseable closeable;
    @Before public void openMocks() {
        closeable = MockitoAnnotations.openMocks(this);
    }
    //...
}

public class ClassToTest {
    @Inject private ClassA classA;
    private final ClassB classB;
    @Inject public ClassToTest(ClassB classB) {
        this.classB = classB;
    }
}

P.S. I don't have control over ClassToTest, I can't rewrite it to move the private member classA to the constructor.

Here I want to mock both classA and classB, while the test is for ClassToTest. Since classA is not set via constructor, I need to use @InjectMocks for Mockito to set it for me. And it did set it correctly with mockClassA I have in the test.

However, the problem is with classB. I set a breakpoint before openMocks(), there I could see classToTest.classA = null and classToTest.classB = mockClassB, which is expected. Then after calling openMocks(), I could see classToTest.classA = mockClassA, which is nice. But classToTest.classB now becomes a new mock object not mockClassB anymore. Since I need to setup the mock returns for mockClassB, replacing it with a new mock is not going to work.

Is there a way to tell Mockito not to swap classB with a new mock?


Solution

  • @RunWith(MockitoJUnitRunner.class) should already initialize all @Mock-and @InjectMocks-annotated fields.

    MockitoAnnotations.openMocks is only required if you do not use the annotation and must manually initialize the fields.

    @RunWith(MockitoJUnitRunner.class)
    public class MyTest {
        @Mock ClassA mockClassA;
        @Mock ClassB mockClassB;
        @InjectMocks ClassToTest classToTest;
        @Before public void setup() {
            when(mockClassB.someCall()).thenReturn(whatever);
        }
    
        @Test public void test() {
            // use this.classToTest
        }
    }
    

    It might be possible to do it without the @Mock annotations by assigning the fields directly:

    @RunWith(MockitoJUnitRunner.class)
    public class MyTest {
        ClassA mockClassA = mock(ClassA.class);
        ClassB mockClassB = mock(ClassB.class);
        @InjectMocks ClassToTest classToTest;
        @Before public void setup() {
            when(mockClassB.someCall()).thenReturn(whatever);
        }
    
        @Test public void test() {
            // use this.classToTest
        }
    }
    

    or

    public class MyTest {
        ClassA mockClassA = mock(ClassA.class);
        ClassB mockClassB = mock(ClassB.class);
        @InjectMocks ClassToTest classToTest;
        AutoCloseable closeable;
        @Before public void setup() {
            closeable = MockitoAnnotations.openMocks(this);
            when(mockClassB.someCall()).thenReturn(whatever);
        }
    
        @Test public void test() {
            // use this.classToTest
        }
    }
    

    Alternatively, create all mock objects manually and use reflection to assign the fields of your class under test.

    Of course it always pays off to design your classes to be testable, which means that their dependencies are passed via the constructor.

    This is similar to Why are my mocked methods not called when executing a unit test? (distinct references to two distinct mock objects, with your test referencing one and your class referencing a different one).