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?
A method can throw only one exception at a time. The Java method must declare any exception type it might throw.
You can't throw two exceptions. I.e. you can't do something like: try { throw new IllegalArgumentException(), new NullPointerException(); } catch (IllegalArgumentException iae) { // ... }
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).
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.
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.
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) {}
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();
}
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) {}
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();
}
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With