Reputation: 30967
I'll try to provide a hackneyed, useless example that reduces the problem nicely :-)
I have a GenericException
, and a MoreSpecificException
which extends GenericException
.
I need to test that SomeService.doThis()
throws a MoreSpecificException
. JUnit lets me do this elegantly like so.
@Test(expected = MoreSpecificException.class)
public void testDoThis() throws GenericException {
new SomeService().doThis();
}
However, I also need to test that SomeService.doThat()
throws a GenericException
, so I tried this.
@Test(expected = GenericException.class)
public void testDoThat() throws GenericException {
new SomeService().doThat();
}
However, I found that if doThat()
actually throws a MoreSpecificException
then the second test still passes. I assume this is because MoreSpecificException
is a GenericException
and the annotation is implemented to respect that relationship.
While this is a sensible default behaviour, I don't want this. I want to test that doThat()
throws a GenericException
and only a GenericException
. If it throws a MoreSpecificException
or any other subclass of GenericException
, I want the test to fail.
Reading the docs it doesn't seem I can do anything with the annotation to change this behaviour, so looks like I'll have to use another solution.
At the moment I'm resorting to the following ugly solution - EDIT made significantly less ugly by Nathan Hughes' answer :-)
@Test
public void testDoThat() {
try {
new SomeService().doThat();
Assert.fail();
} catch(GenericException ex) {
Assert.assertEquals(GenericException.class, ex.getClass());
}
}
Is there a more elegant way to achieve what I want within the JUnit framework?
Upvotes: 5
Views: 903
Reputation: 31587
The most elegant solution ;) Readable, without boilerplate code.
@Test
public void testDoThat() {
when(new SomeService()).doThat();
then(caughtException()).isExactlyInstanceOf(GenericException.class);
}
The code is identical for FEST Assertions 2 + Catch-Exceptions.
org.assertj:assertj-core:1.4.0
com.googlecode.catch-exception:catch-exception:1.2.0
Upvotes: 3
Reputation: 2656
You can use the ExpectedException rule and a custom Hamcrest matcher that specifies which class can be thrown.
The following test will print that you expected an instance of RuntimeException, but got an IllegalArgumentException.
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testThrows() {
thrown.expect(isClass(RuntimeException.class));
throw new IllegalArgumentException("FAKE");
}
public class ClassMatchMatcher extends BaseMatcher<Object> {
private final Class<?> expectedClass;
private ClassMatchMatcher(Class<?> expectedClass) {
this.expectedClass = expectedClass;
}
@Override
public boolean matches(Object item) {
return expectedClass.equals(item.getClass());
}
@Override
public void describeTo(Description description) {
description.appendText("an instance of ")
.appendText(expectedClass.getName());
}
}
public class ExtraMatchers {
public static Matcher<Object> isClass(Class<?> aClass) {
return new ClassMatchMatcher(aClass);
}
}
Edit: Added a static factory method to make the test code cleaner.
Upvotes: 1
Reputation: 96394
You can assert that the class of the Exception is what you expect:
@Test
public void testDoThat() {
try {
new SomeService().doThat();
Assert.fail();
} catch(GenericException ex) {
assertEquals(GenericException.class, ex.getClass());
}
}
Also got rid of the flag, instead having the test fail if no exception is thrown.
Upvotes: 3