简体   繁体   中英

JUnit 4 - expect an Exception of a certain class, but not subclasses

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?

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.

BDD Style Solution

JUnit 4 + Catch Exception + AssertJ

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 .

Source code

Dependencies

org.assertj:assertj-core:1.4.0
com.googlecode.catch-exception:catch-exception:1.2.0

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.

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