Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

junit

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?

like image 278
davnicwil Avatar asked Nov 15 '13 17:11

davnicwil


People also ask

How to assert the type of expected exception In JUnit test?

Then in the test method you can use its expect () and expectMessage () to assert the type of expected exception and the exception message. In older versions of JUnit 4, you can specify the expected exception in the @Test annotation like this:

What is the difference between JUnit and Junit4?

JUnit provides the facility to trace the exception and also to check whether the code is throwing expected exception or not. Junit4 provides an easy and readable way for exception testing, you can use

Why does my JUnit test fail when I try to unit test?

This is because you are expecting an exception from the method you are Unit Testing, otherwise our JUnit test would fail. Example@Test (expected=IllegalArgumentException.class)

How to throw an exception in a Java test?

By using "expected" parameter, you can specify the exception name our test may throw. In above example, you are using " IllegalArgumentException" which will be thrown by the test if a developer uses an argument which is not permitted. Let's understand exception testing by creating a Java class with a method throwing an exception.


3 Answers

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

  • https://gist.github.com/mariuszs/7489706

Dependencies

org.assertj:assertj-core:1.4.0
com.googlecode.catch-exception:catch-exception:1.2.0
like image 111
MariuszS Avatar answered Sep 28 '22 09:09

MariuszS


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.

like image 40
Nathan Hughes Avatar answered Sep 28 '22 10:09

Nathan Hughes


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.

like image 30
Michael Krussel Avatar answered Sep 28 '22 09:09

Michael Krussel