0

I work to test a class with the Junit and I get an NullPointerException.

The class I would like to test is provided below,

@Component
public class EmailageConnector {

    private static final Logger LOG = LoggerFactory.getLogger( EmailageConnector.class );

    private static final String BASIC_AUTHORIZATION = "Basic ";

    @Autowired
    private EmailageConfigHolder emailageConfigHolder;

    @Autowired
    private EntityServiceConnectable<EmailageResponseDto> connector;

    private String authorization;

    @PostConstruct
    void createAuthorization() {
        final String credentials = String.format( "%s%s%s", emailageConfigHolder.getAccountId(), ":", emailageConfigHolder.getApiKey() );
        authorization = BASIC_AUTHORIZATION + encodeBase64String( credentials.getBytes() );
    }

    // ========================================================================

    public ServiceResponse<EmailageResponseDto> call( final RequestInformation requestInfo, final EmailageRequestDto request ) {

        if( LOG.isDebugEnabled() ) {
            LOG.debug( "call emailage [ requestInfo: {} - request: {}]",
                       String.format( "%s", requestInfo ),
                       String.format( "%s", request ) );
        }

        return connector.call( request, createHeaders() );
    }

    // ========================================================================

    private HttpHeaders createHeaders() {
        final HttpHeaders headers = new HttpHeaders();

        headers.add( AUTHORIZATION, authorization );
        return headers;
    }

}

This is my test class,

@RunWith( MockitoJUnitRunner.class )
public class EmailageConnectorTest {

    @InjectMocks
    private EmailageConnector connector;

    @Spy
    private EmailageConfigHolder configHolder;

    @Mock
    EntityServiceConnectable<EmailageResponseDto> serviceConnector;

    @Captor
    private ArgumentCaptor<UriParam<?>> captor;

    @Captor
    private ArgumentCaptor<HttpHeaders> headerCaptor;

    @Before
    public void setUp() {
        configHolder.setApiKey( "api-key-123456" );
        configHolder.setAccountId( "ratepay-group" );
        connector.createAuthorization();
    }

    @Test
    public void call() {

        RequestInformation requestInfo = new TestRequestInformation();

        when( serviceConnector.call( Mockito.any( HttpHeaders.class ), Mockito.any() ) )
                .thenReturn( ServiceResponseFactory.withError( "error", 500 ) );

        ServiceResponse<EmailageResponseDto> response = connector.call( requestInfo, createTestEmailageRequestDto() );

        assertEquals( Integer.valueOf( 500 ), response.getHttpStatus() );
        assertEquals( "error", response.getErrorMessage() );

        verify( serviceConnector ).call( headerCaptor.capture(), captor.capture() );

        assertEquals( 3, captor.getAllValues().size() );
        assertEquals( "api_id", captor.getAllValues().get( 0 ).getName() );
        assertEquals( "api-id-12345", captor.getAllValues().get( 0 ).getValue() );
        assertEquals( "token", captor.getAllValues().get( 1 ).getName() );
        assertEquals( "token-1234", captor.getAllValues().get( 1 ).getValue() );
        assertEquals( "include", captor.getAllValues().get( 2 ).getName() );
        assertEquals( "scores", String.valueOf( ( (SimpleQueryUriParam) captor.getAllValues().get( 2 ) ).getValue()[0] ) );
        assertEquals( GW_ID, headerCaptor.getValue().getFirst( HEADER_GATEWAY_REQUEST_ID ) );
        assertEquals( TRX_ID, headerCaptor.getValue().getFirst( HEADER_TRANSACTION_ID ) );
        assertEquals( "Basic cmF0ZXBheS1ncm91cDphcGkta2V5LTEyMzQ1Ng==", headerCaptor.getValue().getFirst( AUTHORIZATION ) );
    }

    private EmailageRequestDto createTestEmailageRequestDto() {

        EmailageRequestDto dto = new EmailageRequestBuilder().build();
        return dto;
    }
}

The interface for the EntityServiceConnectable is provided below,

public interface EntityServiceConnectable<RESPONSE> extends ServiceConnectable<RESPONSE> {

    ServiceResponse<RESPONSE> call(Object var1, RequestInformation var2, UriParam... var3);

    ServiceResponse<RESPONSE> call(Object var1, HttpHeaders var2, UriParam... var3);

    ServiceResponse<RESPONSE> call(Object var1, UriParam... var2);
}

The test is failing here,

ServiceResponse<EmailageResponseDto> response = connector.call( requestInfo, createTestEmailageRequestDto() );

Here is the error stack for the test,

java.lang.NullPointerException
    at com.ratepay.ella.service.io.EmailageConnectorTest.call(EmailageConnectorTest.java:84)
    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.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    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)

[MockitoHint] EmailageConnectorTest.call (see javadoc for MockitoHint):
[MockitoHint] 1. Unused... -> at com.ratepay.ella.service.io.EmailageConnectorTest.call(EmailageConnectorTest.java:79)
[MockitoHint]  ...args ok? -> at com.ratepay.ella.service.io.EmailageConnector.call(EmailageConnector.java:65)

I debugged the setup and provided the screenshot below,

enter image description here

What do I miss here?

Arefe
  • 11,321
  • 18
  • 114
  • 168
  • Can you add the stacktrace for clarity? – Maciej Kowalski May 28 '19 at 09:21
  • Yes, I added the error stack for the test – Arefe May 28 '19 at 09:27
  • as the setup method do not fail with an NPE, it seems the instance is available at this point. have you debugged to this point, starting in the setup method? If somehow the setup method is not executed we can be sure the Mock object `connector` is never created. – Sunchezz May 28 '19 at 09:27
  • So the connector gets set to null, after the setup method. Go on, step by step and have a look, in wich row the connector gets set to null. – Sunchezz May 28 '19 at 09:33
  • The mock object for the connector is created. I provided a snapshot of the debugging session. – Arefe May 28 '19 at 09:34
  • @Sunchezz it seems so – Arefe May 28 '19 at 09:34
  • You don't seem to call `MockitoAnnotations.initMocks(this);` anywhere in this code? – second May 28 '19 at 09:35
  • i have never worked with Mockito. Do you know if mockito creates its mock objects new, for each test? Maybe it tries to recreate the object and the connector cant be created due to port binding or something? – Sunchezz May 28 '19 at 09:36
  • @second this will be same as the `@RunWith(MockitoJUnitRunner.class) ` – Arefe May 28 '19 at 09:36
  • MockitoAnnotations.initMocks(this); should not be needed if the runner is MockitoJunitRunner.class – Naresh Kumar May 28 '19 at 09:37
  • An alternative could be that you use SpringJunitRunner and mark @Autowired on injectedMock. – Naresh Kumar May 28 '19 at 09:38
  • You could try to add an simple attributeless consturctor to `EmailageConnector`. This way you can set a debug point and have a look when Mockito tries to create a new instance. – Sunchezz May 28 '19 at 09:39
  • Please add the imports that you use on the Test class. – Maciej Kowalski May 28 '19 at 09:40
  • @Arefe: I just made a small test (just using the `@Spy` annotation), for me it does not work when I do not call the initMocks method even when ussing the Runner. – second May 28 '19 at 10:17
  • 1
    By the way, what JUnit version are you using? – second May 28 '19 at 10:19

1 Answers1

2

Edit:
For JUnit5 and mockito refer to this answer:
How to use Mockito with JUnit5
I've updated the example below as well.

Edit2: As the TO did not state which JUnit version or mockito version is being used, I will try to edit the answer if still does not work.


Thanks to @helospark for poining this out.

Using the Runner or the Rule does not work with JUnit5, so probably Mockito Version 2 should be used.

With Mockito Version 1 , an explictly call to the init Method is required.


    @BeforeEach
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }


For Junit5 and the latest Mockito version (2.27) the test class should look like this:

class InnerClass {
    public void doSomething(String fileName) {
    }
}

class SomeClass {

    InnerClass innerClass;

    public boolean doSomething(String fileName) {
        innerClass.doSomething(fileName);
        return true;
    }
}

@ExtendWith(MockitoExtension.class)
class SomeClassTest {

    @InjectMocks
    SomeClass someClass;

    @Mock
    InnerClass innerClass;

    @Test
    public void test() {
        someClass.doSomething("test");
        Mockito.verify(innerClass, Mockito.times(1)).doSomething(Mockito.any());
    }
}
second
  • 4,069
  • 2
  • 9
  • 24
  • 1
    JUnit 5 no longer use the runner model (RunWith), it uses extensions (ExtendWith), so you would need to use `@ExtendWith(MockitoExtension.class)` instead of `@RunWith( MockitoJUnitRunner.class )` for Junit 5 tests to properly initialize mocks. Rules have also been removed from JUnit 5. – helospark May 28 '19 at 10:50
  • @helospark: MockitoExtension is only available in Mockito Version 2. Does that imply that you should not use Mockito Version 1 & JUnit 5 together? – second May 28 '19 at 10:56
  • If it works by manually initializing the mocks, it can be used, however in my opinion it shouldn't, you will see strange issues (exactly what you saw with MockitoJUnitRunner not working, Rules not working, etc.). Also Mockito 1.x is not maintained and the latest release was 5 years ago, so I think it generally should be migrated to 2.x release to get the latest fixes/features. – helospark May 28 '19 at 12:16