Junit assert something after awaiting and handling

2019-09-06 06:59发布

method which throws at first and second call:

public void foo() throws Exception

test:

@test
public void testFooThrowsAtFirstAndSecondTime(){
    boolean thrown;
    try {
       foo();
    } catch (Exception e) {
       thrown = true;
    }
    assertTrue(thrown);

    thrown = false;
    try {
       foo();
    } catch (Exception e) {
       thrown = true;
    }
    assertTrue(thrown);

    foo();
}

Could you help me find a better solution for this? Use of Mockito for a better solution would be also acceptable.

With better i mean, if i could avoid try/catch or even multiple try/catch in my test. In other languages or in jAssert i think even in spring there are statements like:

assertThrows(method(..)) //PseudoCode

I thought with Mockito or JUnit 4.x there is a similar thing.

I know about

@Test(expected=Exception)

But this would only be acceptable if i expect one throw and the test ends after that.

5条回答
Anthone
2楼-- · 2019-09-06 07:04

The key here is that the try block is crucial if you want to resume execution after an exception. You can factor it out into a method or library, but it has to be called within your test method.

Things that work:

  • The tried-and-true fail() idiom that you and nrainier cite, which I prefer:

    try {
      foo();
      fail("foo did not throw an exception");
    } catch (Exception ex) { } 
    
  • catch-exception is a library that, like Mockito, wraps the passed object and puts a try block around each method. Mockito's caveats about final methods and classes apply here too, so this won't always work.

    List myList = new ArrayList();
    catchException(myList).get(1);  // myList is wrapped here
    assert caughtException() instanceof IndexOutOfBoundsException;
    

    Note that catch-exception is in "maintenance mode" because the Java 8 solution (below) is much more solid.

  • Any solution like assertThrows(() -> methodThatThrows()) (Java 8) or:

    assertThrows(new Runnable() {
      @Override public void run() { methodThatThrows(); }
    });
    

    ...in Java 6/7. Importantly, assertThrows is called before methodThatThrows, so it can invoke methodThatThrows. Thanks Stefan for pointing out Fishbowl, but you could easily write an equivalent yourself:

    public void assertThrows(Runnable block) {
      try {
        block.run();
        fail("Block didn't throw.");
      } catch (Exception ex) { }
    }
    

Things that don't work:

  • @Test(expected=YourException.class) will go up the stack to the try block that JUnit wraps your test method in. Control never returns to the test method after that.

  • JUnit4's ExpectedException @Rule looks tempting, but because it wraps the entire test method, you have to set expectations before calling the method that throws the exception.

  • Anything that looks like assertThrows(methodCallThatThrows()). Java will try to get the return value out of methodCallThatThrows before assertThrows is ever invoked, so any try block there can't help.

查看更多
三岁会撩人
3楼-- · 2019-09-06 07:05

@Test(expected=Exception.class)

查看更多
爷的心禁止访问
4楼-- · 2019-09-06 07:17

I don't think a one-liner per method invocation is possible.

I would write the test like this:

@Test
public void testFooThrowsAtFirstAndSecondTime() throws Exception {
  try {
    foo();
    fail("foo did not throw an exception");
  } catch (Exception ex) { }

  try{
    foo(); 
    fail("foo did not throw an exception");
  } catch (Exception ex) { }

  foo();
}
查看更多
爷的心禁止访问
5楼-- · 2019-09-06 07:17

If you are unlucky enough to have to code for some version of java prior to 8, then you cannot do it with one line per exception.

But if you are using java 8, then you can do it as Stefan Birkner suggested.

Now, if you are unwilling to include an entire library for just one method, then here is a method that will work for you, copied from my blog

public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
    try
    {
        runnable.run();
    }
    catch( Throwable throwable )
    {
        if( throwable instanceof AssertionError && throwable.getCause() != null )
            throwable = throwable.getCause();
        assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
        assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
        @SuppressWarnings( "unchecked" )
        T result = (T)throwable;
        return result;
    }
    assert false; //expected exception was not thrown.
    return null; //to keep the compiler happy.
}

So, your test code becomes something like this:

@Test
public void testFooThrowsAtFirstAndSecondTime()
{
    expectException( Exception.class, this::foo );
    expectException( Exception.class, this::foo );
    foo();
}
查看更多
劳资没心,怎么记你
6楼-- · 2019-09-06 07:19

With Java 8 you can use the Fishbowl library.

@Test
public void testFooThrowsAtFirstAndSecondTime(){
  Throwable firstException = exceptionThrownBy(() -> foo());
  assertEquals(Exception.class, firstException.getClass());

  Throwable secondException = exceptionThrownBy(() -> foo());
  assertEquals(Exception.class, secondException.getClass());

  foo()
}

It is possible to use this library with Java 6 and 7, too. But then you have to use anonymous classes.

@Test
public void testFooThrowsAtFirstAndSecondTime(){
  Throwable firstException = exceptionThrownBy(new Statement() {
    public void evaluate() throws Throwable {
      foo();
    }
  });
  assertEquals(Exception.class, firstException.getClass());

  Throwable secondException = exceptionThrownBy(new Statement() {
    public void evaluate() throws Throwable {
      foo();
    }
  });
  assertEquals(Exception.class, secondException.getClass());

  foo()
}
查看更多
登录 后发表回答