Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I test for multiple thrown exceptions in one test method?

I have a well specified interface and against that I write my JUnit tests:

public interface ShortMessageService {

     /**
     * Creates a message. A message is related to a topic
     * Creates a date for the message
     * @throws IllegalArgumentException, if the message is longer then 255 characters.
     * @throws IllegalArgumentException, if the message ist shorter then 10 characters.
     * @throws IllegalArgumentException, if the user doesn't exist
     * @throws IllegalArgumentException, if the topic doesn't exist
     * @throws NullPointerException, if one argument is null.
     * @param userName
     * @param message
     * @return ID of the new created message
     */
     Long createMessage(String userName, String message, String topic);

[...]

}

As you can see the implementation can throw various exceptions for which I have to write tests. My current approach is to write one test method for one possible exception specified in the interface like this:

public abstract class AbstractShortMessageServiceTest
{

    String message;
    String username;
    String topic;

    /**
     * @return A new empty instance of an implementation of ShortMessageService.
     */
    protected abstract ShortMessageService getNewShortMessageService();

    private ShortMessageService messageService;

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Before
    public void setUp() throws Exception
    {
        messageService = getNewShortMessageService();
        message = "Test Message";
        username = "TestUser";
        topic = "TestTopic";
    }

    @Test
    public void testCreateMessage()
    {
        assertEquals(new Long(1L), messageService.createMessage(username, message, topic));
    }

    @Test (expected = IllegalArgumentException.class)
    public void testCreateMessageUserMissing() throws Exception
    {
        messageService.createMessage("", message, topic);
    }

    @Test (expected = IllegalArgumentException.class)
    public void testCreateMessageTopicMissing() throws Exception
    {
        messageService.createMessage(username, message, "");
    }

    @Test (expected = IllegalArgumentException.class)
    public void testCreateMessageTooLong() throws Exception
    {
        String message = "";
        for (int i=0; i<255; i++) {
            message += "a";
        }
        messageService.createMessage(username, message, topic);
    }


    @Test (expected = IllegalArgumentException.class)
    public void testCreateMessageTooShort() throws Exception
    {
        messageService.createMessage(username, "", topic);
    }

    @Test (expected = NullPointerException.class)
    public void testCreateMessageNull() throws Exception
    {
        messageService.createMessage(username, null, topic);
    }

[...]

}

So for now I have to define a lot of test methods for that one method defined in the interface and that feels awkward. Can I combine all these exception tests in one test method or what is the best practice?

like image 501
chillyistkult Avatar asked Dec 16 '13 03:12

chillyistkult


People also ask

How many exceptions can be thrown by a method?

A method can throw only one exception at a time. The Java method must declare any exception type it might throw.

Can you throw multiple exceptions?

You can't throw two exceptions. I.e. you can't do something like: try { throw new IllegalArgumentException(), new NullPointerException(); } catch (IllegalArgumentException iae) { // ... }

What happens if a test method throws an exception?

Yes it is completely fine, and if it does throw the exception the test will be considered as failed. You need to specify that the method throws an Exception even if you know that the specific case does not (this check is done by the compiler).

How do you handle exceptions in test cases?

Create a java class file named TestRunner. java in C:\>JUNIT_WORKSPACE to execute test case(s). Compile the MessageUtil, Test case and Test Runner classes using javac. Now run the Test Runner, which will run the test cases defined in the provided Test Case class.


2 Answers

Unfortunately, the @Test annotation doesn't allow for catching multiple exception types (api reference http://junit.sourceforge.net/javadoc/org/junit/Test.html).

As a first option, I would advocate moving to TestNG. If your team won't allow that, there are few things you can do in JUnit.

Definitely use parameterized test cases so that you don't have to write one test function per test case (http://junit.sourceforge.net/javadoc/org/junit/runners/Parameterized.html). From here, there are a few options.

  1. Group your test data by exception types.

    @Test (expected = IllegalArgumentException.class)
    public void testIllegalArgumentException(String username, String message, String topic) {}
    
    @Test (expected = NullPointerException.class)
    public void testNullPointerException(String username, String message, String topic) {}
    
  2. Combine the exception types in your method signature. (This is what I recommend) Rough outline below ...

    public void testException(String username, String message, String topic, Class<? extends Exception>[] expectedExceptionClasses) {
        try {
            // exception throwing code
        } catch (Exception e) {
            boolean found = false;
            for (Class<?> expectedException : expectedExceptions) {
                if (e instanceof expectedException) {
                    found = true;
                }
            }
            if (found) {
                return;
            }
        }
        Assert.fail();
    }
    
  3. Put all of your tests under the umbrella Exception class (I have a feeling you don't want to do that.).

    @Test (expected = Exception.class)
    public void testException(String username, String message, String topic) {}
    
like image 156
user2910265 Avatar answered Nov 16 '22 01:11

user2910265


It might not be the best idea to combine them all in one method, since you wouldn't really know which test case threw which exception.

For example, if you had the line

messageService.createMessage(username, null, topic);

which should throw a NullPointerException, but instead it threw an IllegalArgumentException, you don't want that to count as a success.

If you'd like to test all the exceptions of that method in one test case, then a good alternative would be to wrap each exception test in a try..catch block.

For example, you could have

@Test
public void testCreateMessageExceptions() {
    // test #1: a null message
    try {
        messageService.createMessage(username, null, topic);
        // if it got this far, that's a problem!
        fail();
    } catch(NullPointerException e) {
        // great, that's what it's meant to do! continue testing
    } catch(Exception e) {
        // if it threw the wrong type of exception, that's a problem!
        fail();
    }

    // test #2: an empty user
    try {
        messageService.createMessage("", message, topic);
        fail();
    } catch(IllegalArgumentException e) {

    } catch(Exception e) {
        fail();
    }

    // ...
}
like image 26
peter Avatar answered Nov 16 '22 01:11

peter