Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

InjectMocks is wrongly injecting the same Mock into 2 different fields of similar type despite creating 2 different mocks

I have a class which has 2 fields of similar types. I have mocked them both. But when I use InjectMocks, inject mocks wrongly injects a single mock into both those fields.

Here is the example code class:


import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

import java.util.Set;
import java.util.function.Consumer;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class TestClass {

    private final Consumer<Set<Integer>> intConsumer;

    private final Consumer<Set<String>> stringConsumer;

    void PrintClass(){
        System.out.println("intConsumers: " + intConsumer);
        System.out.println("stringConsumers: " + stringConsumer);
    }
}

Here is the test class:


import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.Set;
import java.util.function.Consumer;

@RunWith(MockitoJUnitRunner.class)
public class TestClassTest {

    @Mock private Consumer<Set<Integer>> intConsumer;
    @Mock private Consumer<Set<String>> stringConsumer;
    @InjectMocks private TestClass testClass;

    @Test
    public void testPrint(){
        testClass.PrintClass();
    }


}

Here is the output when I run the test: testPrint() - intConsumer is injected into both intConsumer and stringConsumer.

intConsumers: intConsumer
stringConsumers: intConsumer



Process finished with exit code 0

I am using Maven.

<dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>2.7.19</version>
</dependency>

I created this private constructor especially for testing using InjectMocks. I do not want to make it public/package-private so I cannot use field injection. I also do not want to expose these fields using public setters. Also, I do not want to make my fields non-final.

I have tried upgrading the mockito-version to 3.5.10 but it still has this bug. I have also tried making my fields final and using setters - then Injection works fine - but I do not want to expose my setters. I have also tried naming mocks @Mock(name = "mock") with constructor injection but it does not work as well.

Am I missing something here? Is there a way to get it working with private constructor injection?

like image 755
Surbhi Batra Avatar asked Nov 06 '22 05:11

Surbhi Batra


1 Answers

This is an open bug in Mockito.

From what I see, PropertyAndSetterInjection takes generic types and @Mock's name attributes into account, so it works as expected for the field injection. But it doesn't work with constructors because ConstructorInjection is using only SimpleArgumentResolver which is well... very simple and doesn't have any MockCandidateFilter like property injector does.

  • There is issue with generics and mock #1056
  • @InjectMocks ignores @Mock's name of generic type #1066

Typically, you would:

  • drop @InjectMocks and construct TestClass instance in a setup method in test. IMHO this is less invasive approach.
  • alternatively, as mentioned above, field and setter injection work.

Neither of this approaches work with your constraints (private constructor, no setters, final fields).

In such case, you can resort to reflection to build the instance:

@Before
public void setUp() throws IllegalAccessException, 
        InvocationTargetException, 
        InstantiationException,
        NoSuchMethodException {
    final Constructor<TestClass> constructor = TestClass.class.getDeclaredConstructor(Consumer.class, Consumer.class);
    constructor.setAccessible(true);
    testClass = constructor.newInstance(intConsumer, stringConsumer);
}
like image 127
Lesiak Avatar answered Nov 12 '22 17:11

Lesiak