Issue
I have a class that I would like to test.
@Configuration
@Import(EmailageConfiguration.class)
public class EmailageServiceConfiguration {
private static final String EMAILAGE_ACCOUNT_ID_CONFIG_KEY = "emailage.key";
private static final String EMAILAGE_API_KEY_CONFIG_KEY = "emailage.secret";
@Bean
public EmailageConfigHolder emailageConfigHolder(Environment env) {
final EmailageConfigHolder holder = new EmailageConfigHolder();
holder.setApiKey(env.getRequiredProperty(EMAILAGE_API_KEY_CONFIG_KEY));
holder.setAccountId(env.getRequiredProperty(EMAILAGE_ACCOUNT_ID_CONFIG_KEY));
return holder;
}
}
My test class is provided,
@RunWith(MockitoJUnitRunner.class)
public class EmailageServiceConfigurationTest {
@InjectMocks
private EmailageServiceConfiguration configuration;
@Mock
private Environment environment;
@Mock
private EmailageConfigHolder holder;
@Test
public void testEmailageConfigHolder() {
when(environment.getRequiredProperty(anyString())).thenReturn(anyString());
configuration.emailageConfigHolder(environment);
verify(holder, times(1)).setApiKey(anyString());
verify(holder, times(1)).setAccountId(anyString());
}
}
I get the error stack provided below,
Wanted but not invoked: holder.setApiKey(); -> at com.ratepay.ella.service.config.EmailageServiceConfigurationTest.testEmailageConfigHolder(EmailageServiceConfigurationTest.java:48) Actually, there were zero interactions with this mock. Wanted but not invoked: holder.setApiKey(); -> at com.ratepay.ella.service.config.EmailageServiceConfigurationTest.testEmailageConfigHolder(EmailageServiceConfigurationTest.java:48) Actually, there were zero interactions with this mock. at com.ratepay.ella.service.config.EmailageServiceConfigurationTest.testEmailageConfigHolder(EmailageServiceConfigurationTest.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:79) at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:85) at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39) at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
How do I correct the test?
Solution
Here:
final EmailageConfigHolder holder = new EmailageConfigHolder();
Mockito can't inject mocks into a local variable. The documentation is really clear about that:
Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order and as described below.
Basically, by using new()
within the body of your method you wrote hard to test code. Because with Mockito, you have zero options to control what new()
will return in that method body.
Ways out of that:
- make that "holder" a field of your class, then inject via that annotation, or via a constructor accepting a holder instance
- pass an instance as parameter to the method
Or assuming that you can actually create a new Holder object within the production code within your unit test setup, and as you are returning that object, simply assert on the properties of the returned object. From that point of view, you do not need to using mocking here at all. Simply verify that the object coming back from that call has the expected properties!
Or, (not recommended) you could turn to PowerMock(ito) or JMockit, in order to gain control over that call to new()
. But as said: better rework your code to be easy to test.
By the way: the real answer is that you step back and read a good tutorial about Mockito. You can't learn how to use such a framework by trial and error. Learn how to do it right with nice small examples, and then, when you understand how to connect the dots, then apply that to your own code!
Answered By - GhostCat