mockito better expected exception test using spy

34,380

Solution 1

Use catch-exception library, or I guess that the solution you are looking for is your second implementation.

@expected doesn't provide any way to assert on the thrown exception except for its class, so you can't avoit try/catching (not that much boiler plate code !)

Mockito doesn't provide something likes a verifyThrows method.

So you can trade try/catching for an additional library : using catch-exception, you'll be able to catch exception in a single line and have it ready for further assertion(s).

Sample source code

A a = new A();

when(a).doSomethingThatThrows();

then(caughtException())
        .isInstanceOf(IllegalStateException.class)
        .hasMessageContaining("is not allowed to add counterparties")
        .hasNoCause();

Dependencies

'com.googlecode.catch-exception:catch-exception:1.2.0'

Solution 2

If A is your system under test, it doesn't make any sense to mock it, and it rarely makes sense to spy on it. Your implementation in testExpectedException2 is the right one; the boilerplate code is necessary because without a try block Java will not let any code run after the method is intercepted (as I described in this previous SO answer).

Though Mockito won't be any help, JUnit will. The @Test(expected=foo) parameter actually has a more-flexible alternative, the built-in ExpectedException JUnit rule:

public class CheckExceptionsWithMockitoTest {

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

  @Test
  public void testExpectedException1() {
    A a = new A();
    thrown.expect(RuntimeException.class);
    thrown.expectMessage(containsString("cause1"));
    a.doSomethingThatThrows();
  }
}

Mockito would come in handy in a separate test checking whether your method wraps an arbitrary exception while preserving its message, which would look roughly like this:

@Test
public void doSomethingShouldWrapExceptionWithPassedMessage() {
  Dependency dependency = Mockito.mock(Dependency.class);
  when(dependency.call()).thenThrow(new IllegalArgumentException("quux"));
  A a = new A(dependency);
  thrown.expect(RuntimeException.class);
  thrown.expectMessage(containsString("quux"));
  a.doSomethingThatThrows();
}

Be careful to avoid the temptation to make this a common pattern in your tests. If you are catching an exception thrown from your system under test, you're effectively ceding control back to the SUT's consumer. There should be little left to test in the method afterwards, except the properties of the exception and MAYBE the state of your system, both of which should be rare enough that try/catch boilerplate is forgivable.

Solution 3

If you have the opportunity to use scala, scalaTest's fun suite has concise way of testing exceptions using intercept (http://www.scalatest.org/getting_started_with_fun_suite).

It's as simple as

  test(a list get method catches exceptions){
    intercept[IndexOutBoundsException]{
      spyListObject.get(-1)
    }
  }

You could potentially write your tests to your java project in scala if you are looking for easy to write / clear test. But this may present other challenges.

Solution 4

Updated answer for 06/19/2015 (if you're using java 8)

Using assertj-core-3.0.0 + Java 8 Lambdas

@Test
public void shouldThrowIllegalArgumentExceptionWhenPassingBadArg() {
      assertThatThrownBy(() -> myService.sumTingWong("badArg"))
                                  .isInstanceOf(IllegalArgumentException.class);
}

Reference: http://blog.codeleak.pl/2015/04/junit-testing-exceptions-with-java-8.html

Share:
34,380
raisercostin
Author by

raisercostin

Updated on September 12, 2020

Comments

  • raisercostin
    raisercostin over 3 years

    How can I make the 3rd test to check for the existence of cause1 in the message of the exception? I also listed in the first two tests that have drawbacks. First is not checking for the message second needs a lot of boilerplate code.

    public class CheckExceptionsWithMockitoTest {
    
        @Test(expected = RuntimeException.class)
        public void testExpectedException1() {
            A a = new A();
            a.doSomethingThatThrows();
        }
    
        @Test
        public void testExpectedException2() {
            A a = new A();
            try {
                a.doSomethingThatThrows();
                fail("no exception thrown");
            } catch (RuntimeException e) {
                assertThat(e.getMessage(), org.hamcrest.Matchers.containsString("cause1"));
            }
        }
    
        @Test
        public void testExpectedException3() {
            A a = new A();
            A spyA = org.mockito.Mockito.spy(a);
            // valid but doesnt work
            // doThrow(new IllegalArgumentException()).when(spyA).doSomethingThatThrows();
            // invalid but in the spirit of what i want 
            //chekThrow(RuntimeException.class,containsString("cause1")).when(spyA).doSomethingThatThrows();
        }
    
    }
    

    I couldn't find in Mockito something that works but there is something that looks like could be possible (at the level of syntax) and capabilities.


    Using catchexception I created the test like this

    import static com.googlecode.catchexception.CatchException.*;
    import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;
    import static org.hamcrest.Matchers.*;
    import static org.junit.Assert.*;
    
    import org.junit.*;
    public class CheckExceptionsWithMockitoTest{  
        //...
        @Test
        public void testExpectedException3() {
            A a = new A();
            verifyException(a,IllegalArgumentException.class)
                .doSomethingThatThrows();
            //if more details to be analized are needed
            assertThat(
                (IllegalStateException) caughtException(),
                allOf(
                    is(IllegalStateException.class),
                    hasMessageThat(
                            containsString("is not allowed to add counterparties")), 
                    hasNoCause()));
            //more asserts could come
            assertNotNull(a);
        }
    }