Reputation: 625
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
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
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
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
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
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
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