I'm writing tests for a project with a heavy use of dependency injection.
So normally I would just inject the object that I'm testing:
public class RegistrationTest
extends WithApplication {
private RegistrationController controller;
@Before
public void setUp() throws Exception {
Injector injector = app.injector();
controller = injector.instanceOf(RegistrationController.class);
}
@Test
public void openRegistrationView() {
Result result = controller.registrationForm();
assertEquals(OK, result.status());
}
}
Now I need to mock a return value of a method of one of the dependencies of the class under test.
To do this I can use @InjectMocks
and @RunWith(MockitoJUnitRunner.class)
to inject mocked dependencies.
@RunWith(MockitoJUnitRunner.class)
public class RegistrationTest
extends WithApplication {
@InjectMocks
private RegistrationController controller;
@Mock
private FormFactory formFactory;
@Mock
private RegistrationIpCache registrationIpCache;
@Test
public void openRegistrationView() {
doReturn("test").when(registrationIpCache).getLast();
Result result = controller.registrationForm();
assertEquals(OK, result.status());
}
}
But while I only want to mock one method call of RegistrationIpCache
I need to mock the whole class and also I need to mock all other dependencies of RegistrationController
like FormFactory
in my example. Now every method of all mocked classes doesn't work as expected.
Sure I can use doCallRealMethod()
to mock every method of all dependencies
doCallRealMethod().when(formFactory).form(Registration.class);
But as you already can imagine the work and the amount of boilerplate code needed for this to work is extreme.
Isn't it possible to inject my class normally and then only spy one dependency or mock one method of one dependency?
You need to use @Spy
instead of a @Mock
.
@InjectMocks
private RegistrationController controller;
@Mock
private FormFactory formFactory;
@Spy
private RegistrationIpCache registrationIpCache;
But be aware that in this case @Spy
will try to use default constructor.
If the default constructor is not available use explicit constructor call:
@Spy
private RegistrationIpCache registrationIpCache = new RegistrationIpCache(dependencies);
Here is the cases when Mockito isn't able to instantiate your class from @Spy
javadoc:
Mockito will try to find zero argument constructor (even private) and create an instance for you. But Mockito cannot instantiate inner classes, local classes, abstract classes and interfaces.
Here is the example of manually injecting dependencies:
@RunWith(MockitoJUnitRunner.class)
public class RegistrationTest {
private RegistrationController controller;
private RegistrationIpCache spyRegistrationIpCache; //this is the dependencies that you need to spy
@Before
public void setUp() throws Exception {
spyRegistrationIpCache = spy(realInstanceOfregistrationIpCache);
controller = new RegistrationController(registrationIpCache, realInstanceOfFormFactory);
}
}
In this case there is much more code that you have to write, but this give you much more control over the object instantiation and dependency injection. Note that using constructor instantiation is not required. Since you do it manually you can instantiate it however you want.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With