How do I test exceptions in a parameterized test?

2019-03-25 04:58发布

问题:

In JUnit4 you can write parameterized unit tests by providing parameters collection in one method, which will be passed to the constructor of the test and testing in another method. If I have a parameter for which I expect an exception to be thrown, how do I specify that?

回答1:

if (parameter == EXCEPTION_EXPECTED) {
    try {
        method(parameter);
        fail("didn't throw an exception!");
    } catch (ExpectedException ee) {
        // Test succeded!
    }
}


回答2:

this is how i use junit parameterized test with expected exceptions:

@RunWith(Parameterized.class)
public class CalcDivTest {

@Parameter(0)
public int num1;
@Parameter(1)
public int num2;

@Parameter(2)
public int expectedResult;

@Parameter(3)
public Class<? extends Exception> expectedException;
@Parameter(4)
public String expectedExceptionMsg;

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

@Parameters
public static Iterable<Object[]> data() {
    return Arrays.asList(new Object[][] {
        // calculation scenarios:
        { 120, 10, 12, null, null }, // simple div  
        { 120, 0, -1, ArithmeticException.class, "/ by zero" }, // div by zero          
    });

}

@Test
public void testDiv() throws CCalculationException {

    //setup expected exception
    if (expectedException != null) {
        thrown.expect(expectedException);
        thrown.expectMessage(expectedExceptionMsg);
    }

    assertEquals("calculation result is not as", expectedResult, div(num1, num2) );

}

private int div(int a, int b) {
    return a/b;
}
}


回答3:

In contrast to what other suggest, I would not introduce any kind of logic to tests - even simple ifs!

What you should have are two testing methods:

  • first one takes valid parameters (and expects some output)
  • second takes invalid parameters (and expects exceptions)

Not sure if JUnit with its constructor-based parametrized testing is able to do this. Probably you would have to create two test classes for this. Go with JUnit Params or TestNG which offer much more convenient solution.



回答4:

Gabriel, please look at TestWatcher rule (since JUnit 4.9). Here is the sample code quoted from http://junit-team.github.io/junit/javadoc/4.11/org/junit/rules/TestWatcher.html:

public static class WatchmanTest {
    private static String watchedLog;

    @Rule
    public TestWatcher watchman= new TestWatcher() {
        @Override
        protected void failed(Throwable e, Description description) {
            watchedLog+= description + "\n";
        }

        @Override
        protected void succeeded(Description description) {
            watchedLog+= description + " " + "success!\n";
        }
     };


     @Test
     public void fails() {
         fail();
     }

     @Test
     public void succeeds() {
     }
 }

Another approach would be to use ErrorCollector from JUnit 4.7: @Rule public ExpectedException thrown = ExpectedException.none();

@Test
public void testCollectingErrors() {
    thrown.handleAssertionErrors();
    thrown.expect(MultipleFailureException.class); // or #expectMessage()/#expectCause()

    collector.checkThat("a", equalTo("b"));
    //...
}


回答5:

If you used catch-exception instead of the corresponding annotations and rules of JUnit4, then your code would look like this:

catchException(obj).method(parameter);

if (parameter != EXCEPTION_EXPECTED) {
    assert caughtException() instanceof ExpectedException;
}
// more assertions


回答6:

I agree with Tomek, and would go with two tests. The first tests for cases where no exceptions are expected. The second tests for values that should result in exceptions being thrown (i.e., and fails if they are not thrown).

Below is a simple example, where the implementation of ExceptionThrower.throwAnInstanceException(int) simply throws an IllegalArgumentException when the supplied int is less-than-1. In your implementation, all supplied values should trigger the exception.

@ParameterizedTest
@ValueSource(ints = {0, 1})
public void parameterizedIntExceptionTest(int testValue) {
    ExceptionThrower exceptionThrower = new ExceptionThrower();

    assertThrows(IllegalArgumentException.class, () -> {
        exceptionThrower.throwAnInstanceException(testValue);
    });
}

If you wanted to supply multiple arguments, then you'd be looking at using a MethodSource vice a ValueSource for the test.