Mockito doThrow on void method with null argument not throwing exception: What am I doing wrong?

2k views Asked by At

I am unsure if I have found a bug or if I am just doing it wrong. I am trying to have a mock throw an exception when a method is invoked (relatively easy normally), except the method is of void return type, and the object to be passed in (why the error is thrown) is null. This can be typed through isNull() to compile, but the error is still not thrown.

package some.example;

import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;

import javax.jms.Message;
import javax.jms.MessageListener;

import org.mockito.Mock;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.jms.listener.SessionAwareMessageListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class TestClass {
    @Mock
    private AbstractMessageListenerContainer messageContainer;

    @BeforeClass
    public void setUpMocks() {
        initMocks(this);

        doThrow(new IllegalArgumentException()).when(messageContainer).setupMessageListener(
                isNull(MessageListener.class));
        doThrow(new IllegalArgumentException()).when(messageContainer).setupMessageListener(
                isNull(SessionAwareMessageListener.class));
    }

    @AfterMethod
    public void resetMocks() {
        reset(messageContainer);
    }

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testSetUpQueueConsumerWithNullMessageListener() throws Exception {
        final MessageListener messageListener = null;
        try (final QueueConsumer consumer = new QueueConsumer(messageContainer, messageListener)) {
        } finally {
            verify(messageContainer).setupMessageListener(messageListener);
        }
    }

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testSetUpQueueConsumerWithNullSessionAwareMessageListener() throws Exception {
        final SessionAwareMessageListener<Message> messageListener = null;
        try (final QueueConsumer consumer = new QueueConsumer(messageContainer, messageListener)) {
        } finally {
            verify(messageContainer).setupMessageListener(messageListener);
        }
    }

    public class QueueConsumer implements AutoCloseable {
        private final AbstractMessageListenerContainer messageContainer;

        QueueConsumer(final AbstractMessageListenerContainer messageContainer,
                final SessionAwareMessageListener<? extends Message> messageListener) {
            this(messageContainer);
            this.messageContainer.setupMessageListener(messageListener);
        }

        QueueConsumer(final AbstractMessageListenerContainer messageContainer, final MessageListener messageListener) {
            this(messageContainer);
            this.messageContainer.setupMessageListener(messageListener);
        }

        private QueueConsumer(final AbstractMessageListenerContainer messageContainer) {
            if (messageContainer == null) {
                throw new IllegalArgumentException("MessageListenerContainer cannot be null");
            }
            this.messageContainer = messageContainer;
        }

        public void stop() {
            messageContainer.stop();
        }

        @Override
        public void close() throws Exception {
            stop();
        }
    }
}

Relevant maven dependencies:

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.8.21</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.0.8-beta</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jms</artifactId>
    <version>${org.springframework.version}</version>
</dependency>

EDIT

I added the stripped down real code. I toyed with the code more this morning, and discovered I was using Mockito's reset() incorrectly. I thought it would reset the mock to its unverified state, but it also erases the doThrow() stubs. I had to change the initialization methods as follows:

@BeforeClass
public void setUpMocks() {
    initMocks(this);
}

@BeforeMethod
public void setUpThrows() {
    doThrow(new IllegalArgumentException()).when(messageContainer).setupMessageListener(
            isNull(MessageListener.class));
    doThrow(new IllegalArgumentException()).when(messageContainer).setupMessageListener(
            isNull(SessionAwareMessageListener.class));
}
1

There are 1 answers

1
Jeff Bowman On BEST ANSWER

As resolved in the comments and edit: Be careful to note that the reset method resets the mock entirely, including both stubs and interactions (the latter for verifications).

As warned in the Mockito documentation, reset is generally a bad sign in your tests: If it comes in the middle of a test method, generally that method should be split up into multiple smaller tests, and if it comes in an @After or @AfterMethod it means that your test setup is polluting between tests. Keep your mocks in instance fields, not static fields, initialized in a @Before (JUnit) or @BeforeMethod (TestNG) method to ensure they're fully overwritten before every single test in a test class.