Wildchild
Wildchild

Reputation: 625

Mockito Inject mock into Spy object

I'm writing a test case for a Class which has a 2 level of dependency injection. I use @Spy annotation for the 1 level dependency injection object, and I would like to Mock the 2nd level of injection. However, I kept getting null pointer exception on the 2nd level. Is there any way that I inject the mock into the @Spy object?

public class CarTestCase{
    @Mock
    private Configuration configuration;

    @Spy 
    private Engine engine;

    @InjectMocks 
    private Car car;

    @Test
    public void test(){

       Mockito.when(configuration.getProperties("")).return("Something");
       car.drive();
    }

}

public class Car{
    @Inject
    private Engine engine;

    public void drive(){
        engine.start();
    }
}

public class Engine{
    @Inject 
    private Configuration configuration;

    public void start(){
        configuration.getProperties();   // null pointer exception
    }

}

Upvotes: 61

Views: 79759

Answers (6)

Mykhaylo Adamovych
Mykhaylo Adamovych

Reputation: 20986

Junit5 + mockito-junit-upiter-4.2.0 works well

@ExtendWith(MockitoExtension.class)
public class MyTest {

@Spy
@InjectMocks
private SomeClass spy = new SomeClass();

Upvotes: 0

Ahmad Shahwan
Ahmad Shahwan

Reputation: 1984

The (simplest) solution that worked for me.

@InjectMocks
private MySpy spy = Mockito.spy(new MySpy());

No need for MockitoAnnotations.initMocks(this) in this case, as long as test class is annotated with @RunWith(MockitoJUnitRunner.class).

Upvotes: 44

Yoory N.
Yoory N.

Reputation: 5484

I've also wandered how to inject a mock into a spy.

The following approach will not work:

@Spy
@InjectMocks
private MySpy spy;

But the desired behavior can be achieved by a "hybrid" approach, when using both annotation and manual mocking. The following works perfectly:

@Mock
private NeedToBeMocked needToBeMocked;

@InjectMocks
private MySpy mySpy;

@InjectMocks
private SubjectUnderTest sut;

@BeforeMethod
public void setUp() {
    mySpy = Mockito.spy(new MySpy());
    MockitoAnnotations.initMocks(this);
}

(SubjectUnderTest here depends on MySpy, and MySpy in its turn depends on NeedToBeMocked).

UPD: Personally, I think that if you have to do such a magic too often, it might be a sign that there is something wrong with dependenicies between your classes and it is worth to perform a little bit of refactoring to improve your code.

Upvotes: 63

DavidBu
DavidBu

Reputation: 526

I think I just found the definitive answer. I tried Yoory approach but changed the order of the annotations :

@InjectMocks
@Spy
private MySpy spy;

I assume that Mockito first creates the mock, and adds a spy on top of that. So there is no need to instantiate the MySpy object.

Upvotes: 5

Jack Yang
Jack Yang

Reputation: 181

I also met this issue during the unit testing with Spring boot framework, but I found one solution for using both @Spy and @InjectMocks

Previous answer from Yoory N.

@Spy
@InjectMocks
private MySpy spy;

Because InjectMocks need to have instance created, so the solution works for me is at below,

@Spy
@InjectMocks
private MySpy spy = new MySpy();

Upvotes: 18

Sergii Bishyr
Sergii Bishyr

Reputation: 8651

Mockito cannot perform such a tricky injections as it's not an injection framework. So, you need to refactor your code to make it more testable. It's easy done by using constructor injection:

public class Engine{
    private Configuration configuration;

    @Inject 
    public Engine(Configuration configuration) {
        this.configuration = configuration;
    }
    ........
}

public class Car{
    private Engine engine;

    @Inject    
    public Car(Engine engine) {
        this.engine = engine;
    }
}

In this case you have to handle the mocking and injection manually:

public class CarTestCase{

    private Configuration configuration;

    private Engine engine;

    private Car car;

    @Before
    public void setUp(){
        configuration = mock(Configuration.class);
        engine = spy(new Engine(configuration));
        car = new Car(engine);
    }

    @Test
    public void test(){

       Mockito.when(configuration.getProperties("")).return("Something");
       car.drive();
    }

}

Upvotes: 28

Related Questions