27

I have a test:

@Rule
public ExpectedException thrown = ExpectedException.none();
...
@Test
public void testMethod()
{
    final String error = "error message";
    Throwable expectedCause = new IllegalStateException(error);
    thrown.expectCause(org.hamcrest.Matchers.<Throwable>equalTo(expectedCause));
    someServiceThatTrowsException.foo();
}

When run via mvn the test method, I'm getting the error:

java.lang.NoSuchMethodError: org.junit.rules.ExpectedException.expectCause(Lorg/hamcrest/Matcher;)V

Test compiles fine.

Please help me, cannot understand how to test the cause of the exception?

Alexandr
  • 9,213
  • 12
  • 62
  • 102
  • I currently have the same issue... For me the issue occurs when I use the an inherited dependency from the parent project, but now when I re-declare the dependency in the local POM.xml. Did you find a solution?? – Dennis Apr 09 '14 at 09:23
  • @Dennis, no I haven't. I use the standard try/catch java idiom. If an exception is not thrown the junit fail() is invoked. In the catch block I analyze the cause. – Alexandr Apr 10 '14 at 08:26
  • 1
    thanks. I will stick with my duplicated dependency for now, but will fall back to a try-catch block as you have done. – Dennis Apr 10 '14 at 08:51

11 Answers11

29

Try it this way:

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

@Test public void testMethod() throws Throwable {
    final String error = "error message";
    Throwable expectedCause = new IllegalStateException(error);
    thrown.expectCause(IsEqual.equalTo(expectedCause));
    throw new RuntimeException(expectedCause);
}

Consider not to check against the cause by equals but by IsInstanceOf and / or comapring the exception message if necessary. Comparing the cause by equals check the stacktrace as well, which may be more than you would like to test / check. Like this for example:

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

@Test public void testMethod() throws Throwable {
    final String error = "error message";
    thrown.expectCause(IsInstanceOf.<Throwable>instanceOf(IllegalStateException.class));
    thrown.expectMessage(error);
    throw new RuntimeException(new IllegalStateException(error));
}
Harmlezz
  • 7,972
  • 27
  • 35
21

A little bit more briefly with static imports and checking both the class and the message of the cause exception:

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

@Test
public void testThatThrowsNiceExceptionWithCauseAndMessages(){

     expectedException.expect(RuntimeException.class );
     expectedException.expectMessage("Exception message");                                           
     expectedException.expectCause(allOf(instanceOf(IllegalStateException.class),
                                        hasProperty("message", is("Cause message"))) );
     
     throw new RuntimeException("Exception message", new IllegalStateException("Cause message"));
}

You could even use the hasProperty matcher to assert nested causes or to test the "getLocalizedMessage" method.

Update::

Junit has migrated the assertThrows from JUnit 5 to 4. I would really recommended this options to make the test clearer (and easier to migrate to Junit5 in the future).

RuntimeException ex = assertThrows(RuntimeException.class, () -> {
   throw new RuntimeException("Exception message", new IllegalStateException("Cause message"));
});

assertThat(ex.getMessage(), contains("Exception message"));
assertThat(ex.getMessage().getCause(), instanceOf(IllegalStateException.class),;
assertThat(ex.getMessage().getCause().getMessage(), is("Cause message"));
borjab
  • 11,149
  • 6
  • 71
  • 98
17

You can use a custom matcher as described here (http://www.javacodegeeks.com/2014/03/junit-expectedexception-rule-beyond-basics.html) to test for the cause of an exception.

Custom matcher

private static class CauseMatcher extends TypeSafeMatcher<Throwable> {

    private final Class<? extends Throwable> type;
    private final String expectedMessage;

    public CauseMatcher(Class<? extends Throwable> type, String expectedMessage) {
        this.type = type;
        this.expectedMessage = expectedMessage;
    }

    @Override
    protected boolean matchesSafely(Throwable item) {
        return item.getClass().isAssignableFrom(type)
                && item.getMessage().contains(expectedMessage);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("expects type ")
                .appendValue(type)
                .appendText(" and a message ")
                .appendValue(expectedMessage);
    }
}

Test case

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

@Test
public void verifiesCauseTypeAndAMessage() {
    thrown.expect(RuntimeException.class);
    thrown.expectCause(new CauseMatcher(IllegalStateException.class, "Illegal state"));

    throw new RuntimeException("Runtime exception occurred",
            new IllegalStateException("Illegal state"));
}
jansohn
  • 2,246
  • 2
  • 28
  • 40
11

It's JUnit version problem.

ExpectedException.expectCause() is since 4.11.

No such method in 4.10 or lower.

You should ensure your runtime JUnit version >= 4.11, same as your compile version.

chrisjleu
  • 4,329
  • 7
  • 42
  • 55
卢声远 Shengyuan Lu
  • 31,208
  • 22
  • 85
  • 130
6

To summarize all.

With JUnit 4 ( hamcrest 1.3 , and be careful, JUnit 4 depend on hamcrest-core which not include org.hamcrest.beans package)

So, you need to import:

<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest-all</artifactId>
  <version>1.3</version>
  <scope>test</scope>
</dependency>

Code:

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;

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

@Test
public void testThatThrowsNiceExceptionWithCauseAndMessages(){

  expectedException.expect(RuntimeException.class );
  expectedException.expectMessage("Exception message");                                           
  expectedException.expectCause(
    allOf(
      isA(IllegalStateException.class),
      hasProperty("message", is("Cause message"))
    )
  );

  throw 
    new RuntimeException("Exception message", 
      new IllegalStateException("Cause message"));
}
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Gmugra
  • 468
  • 7
  • 10
5

Normally I like more the following construction:

expectedException.expectCause(isA(NullPointerException.class));

wikier
  • 2,517
  • 2
  • 26
  • 39
4

The any(Class<T>) matcher from hamcrest works nicely:

@Rule
public ExpectedException thrown = ExpectedException.none();
...
@Test
public void testMethod()
{
    thrown.expect(RuntimeException.class);
    thrown.expectCause(org.hamcrest.Matchers.any(IllegalStateException.class));
}
Ali Cheaito
  • 3,746
  • 3
  • 25
  • 30
1

Import

<dependency>
  <groupId>it.ozimov</groupId>
  <artifactId>java7-hamcrest-matchers</artifactId>
  <version>1.3.0</version>
  <scope>test</scope>
</dependency>

And then:

@Rule
public ExpectedException thrown = ExpectedException.none();
...
@Test
public void testMethod()
{
    final String errorMessage = "error message";
    Class<? extends Throwable> expectedCause = IllegalStateException.class;
    thrown.expectCause(ExpectedException.exceptionWithMessage(expectedCause, errorMessage));
    someServiceThatTrowsException.foo();
}

It works also with subtype of the cause. In other solutions I observed that they accept a supertype, that is wrong in my opinion.

Message must be equal or contained in the cause's error message.

JeanValjean
  • 17,172
  • 23
  • 113
  • 157
0

You can do this entirely with built-in matchers org.hamcrest.Matchers.instanceOf and org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage:

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.both;
import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;

public class temp {
    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    @Test
    public void youCannotDivideByZero() {
        expectedException.expect(RuntimeException.class);
        expectedException.expectMessage(equalTo("Division exception"));
        expectedException.expectCause(both(hasMessage(equalTo("/ by zero"))).and(instanceOf(ArithmeticException.class)));
        divide(1, 0);
    }

    private float divide(int first, int second) {
        try {
            return first / second;
        } catch(ArithmeticException e) {
            throw new RuntimeException("Division exception", e);
        }
    }
}
migwellian
  • 143
  • 6
0

The answers so far all use the deprecated method - ExpectedException.none().

This should now be done with assertThrows, which returns the actual exception, which can then be tested in the usual ways.

This gives a lot more expressive power to the test case writer as the exception is not required to be the end of the test case.


@Test
public void testWithIOException() {
    // ...
    IOException exception = assertThrows(IOException.class, 
        () -> someServiceThatThrowsException.foo());

    assertTrue(exception.getMessage().contains("my error message"));
}

many thanks to: https://jsparrow.github.io/rules/replace-j-unit-expected-exception.html#code-changes

John Lehmann
  • 896
  • 6
  • 5
-1

Example of verifying the message and cause of a thrown exception for Kotlin language:

@get:Rule
val exceptionRule: ExpectedException = ExpectedException.none()

@Test
fun `test method`() {
    exceptionRule.expect(NestedServletException::class.java)
    exceptionRule.expectMessage("error msg")
    exceptionRule.expectCause(instanceOf(IllegalStateException::class.java))
    // ...
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259