1
votes

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?

2
The mock object holder is not the same as the local variable holder in your method under test. That one is constructed using new EmailageConfigHolder(), so your mock's methods will never be called.sfiss
@sfiss you are correct, and I get a more detailed answer later on.Arefe

2 Answers

5
votes

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!

0
votes

While the other answer better fits with the situation, I'm not able to update the code and finally, wrote this test code.

@RunWith( MockitoJUnitRunner.class )
public class EmailageServiceConfigurationTest {

    private static final String ACCOUNT_ID = "emailage.key";
    private static final String API_KEY = "emailage.secret";

    @InjectMocks
    private EmailageServiceConfiguration configuration;

    @Mock
    private Environment environment;

    @Test
    public void testEmailageConfigHolder() {

        configuration.emailageConfigHolder( environment );

        verify( environment, times( 1 ) ).getRequiredProperty( API_KEY );
        verify( environment, times( 1 ) ).getRequiredProperty( ACCOUNT_ID );
    }
}