简体   繁体   中英

Mocking void method returning a null pointer exception

I am facing an issue when trying to unit test a function call. The call is failing for a void method invocation messageProducer.sendMessage() even though it has been stubbed.

Please find below a simplified snapshot of my code. I am using a doAnswer() stubbing to mock the void method (based on earlier answers on StackOverflow).

I even tried the other options of doThrow() and doNothing() stubbings, but they also fail with the same NPE when calling the stubbed method :(.

Appreciate if someone could suggest a solution/workaround. Many thanks.

Test Class

// Test class
@RunWith(MockitoJUnitRunner.class)
public class RetriggerRequestTest {
    @Mock
    private MessageProducer messageProducer;
 
    @InjectMocks
    private MigrationRequestServiceImpl migrationRequestService;
 
    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void sendRetriggerRequest() throws Exception {
        // Below two stubbings also not Work, NPE encountered!
        //doNothing().when(messageProducer).sendMessage(any(), anyLong());
        //doThrow(new Exception()).doNothing().when(messageProducer).sendMessage(any(), anyLong());

        doAnswer(new Answer<Void>() {
            public Void answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                System.out.println("called with arguments: " + Arrays.toString(args));
                return null;
            }
        }).when(messageProducer).sendMessage(any(EMSEvent.class), anyLong());

        try {
            // Gets Null pointer exception
            migrationRequestService.retriggerRequest(emsRetriggerRequest);
        }
        catch (Exception ex) {
            fail(ex.getMessage());
        }
    }

Implementation Class being tested, the stubbed method call from this class throws NPE as indicated in code comments

@Service
@Transactional
public class MigrationRequestServiceImpl implements MigrationRequestService {
    @Autowired
    MessageProducer messageProducer;

    @Override
    public void retriggerRequest(EMSRetriggerRequestData emsRetriggerRequestData) throws EMSException {
        // Does a bunch of things
        submitTaskScheduledEventsToQueue(taskList);
    }

    private void submitTaskScheduledEventsToQueue(List<Task> taskList) {
        System.out.println("Debugging 1...");
        taskList.stream().forEach(task -> {
            System.out.println("Debugging 2...");
            Map<String, Object> detailsMap = new HashMap<String, Object>();
            EMSEvent event = new EMSEvent(EMSEventType.TASK_SCHEDULED);
            event.setDetails(detailsMap);

            LOGGER.info(ContextRetriever.getServiceContext(), ContextRetriever.getRequestContext(), "*** Re-submitting Task: *** " + task.getId());

            // ****Gives a null pointer exception here****
            messageProducer.sendMessage(event, eventsConfigProperties.getScheduledEventDelay());
        });
        System.out.println("Debugging 3...");
    }
}

Autowired class that is injected into the test class and whose method is throwing the NPE

@Service
public class MessageProducer {
private static final Logger logger = LoggerFactory.getLogger(MessageProducer.class);

        private final RabbitTemplate rabbitTemplate;

        @Autowired
        public MessageProducer(RabbitTemplate rabbitTemplate) {
                this.rabbitTemplate = rabbitTemplate;
    }   

    public void sendMessage(EMSEvent emsEvent, Long delay) {
        // code to send message to RabbitMQ here
    }   
}

Do not use doAnswer if you simply want to capture the arguments and process/verify them in some way. Mockito has a defined feature called ArgumentCaptor that is designed just for that. By using it you will not need to haggle around with that void method the way you do:

@Mock private MessageProducer messageProducer;

@Captor private ArgumentCaptor<Event> eventCaptor;
@Captor private ArgumentCaptor<Long> longCaptor;

@InjectMocks
private MigrationRequestServiceImpl migrationRequestService;

@Test
public void sendRetriggerRequest() throws Exception {
   // When
   migrationRequestService.retriggerRequest(emsRetriggerRequest);

   // Then
   verify(messageProducer).sendMessage(eventCaptor.capture(), longCaptor.capture());

   Event e = eventCaptor().getValue();
   Long l = longCaptor().getValue();
}

Thank you Maciej for the answer. Actually I don't want to do anything with the arguments, I just need to skip this method call. I just used doAnswer with some dummy code since doNothing() or doThrow() did not work with this method.

I was able to resolve the issue however. One of the Autowired components (eventsConfigProperties) for the class which was being injected with Mocks (MigrationRequestServiceImpl) was not being mocked in the test class! Thanks to @daniu for pointing this out.

The stack trace from Mockito was not very helpful in debugging the issue, it just gave a null pointer exception right on the method invocation which caused me to think there may be other issues!

Apologize for the mistake, my bad, but thank you and good to know about the ArgumentCaptor, would probably need it for future testing!

Had to add this entry which was autowired into the MigrationRequestService class.

// Test class
@RunWith(MockitoJUnitRunner.class)
public class RetriggerRequestTest {
    @Autowired
    EventsConfigProperties eventsConfigProperties;

    // Other declarations
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM