简体   繁体   中英

JUnit4 @Test(expected=MyException.class) VS try/catch

I'm pondering on exception handling and unit tests best practices because we're trying to get some code best practices in place.

A previous article regarding best practices, found on our company wiki, stated "Do not use try/catch, but use Junit4 @Test(expect=MyException.class)", without further information. I'm not convinced.

Many of our custom exception have an Enum in order to identify the failure cause. As a result, I would rather see a test like :

@Test
public void testDoSomethingFailsBecauseZzz() {
try{
   doSomething();
} catch(OurCustomException e){
   assertEquals("Omg it failed, but not like we planned", FailureEnum.ZZZ, e.getFailure());
}
}

than :

@Test(expected = OurCustomException.class)
public void testDoSomethingFailsBecauseZzz() {
   doSomething();
}

when doSomethig() looks like :

public void doSomething throws OurCustomException {
  if(Aaa) {
     throw OurCustomException(FailureEnum.AAA);
  } 
  if(Zzz) {
     throw OurCustomException(FailureEnum.ZZZ);
  }
  // ...
}

On a side note, I am more than convinced that on some cases @Test(expected=blabla.class) IS the best choice (for example when the exception is precise and there can be no doubt about what's causing it).

Am I missing something here or should I push the use of try/catch when necessary ?

It sounds like your enum is being used as an alternative to an exception hierarchy? Perhaps if you had an exception hierarchy the @Test(expected=XYZ.class) would become more useful?

  • If you simply want to check that an exception of a certain type was thrown, use the annotation's expected property.
  • If you want to check properties of the thrown exception (eg the message, or a custom member value), catch it in the test and make assertions.

In your case, it seems like you want the latter (to assert that the exception has a certain FailureEnum value); there's nothing wrong with using the try/catch .

The generalization that you should "not use try/catch" (interpreted as "never") is bunk.

Jeff is right though; the organization of your exception hierarchy is suspect. However, you seem to recognize this. :)

If you want to check the raw exception type, then the expected method is appropriate. Otherwise, if you need to test something about the exception (and regardless of the enum weirdness testing the message content is common) you can do the try catch, but that is a bit old-school. The new JUnit way to do it is with a MethodRule . The one that comes in the API ( ExpectedException ) is about testing the message specifically, but you can easily look at the code and adapt that implementation to check for failure enum s.

I came across this when searching how to handle exceptions.

As @Yishai mentioned, the preferred way to expect exceptions is using JUnit rules and ExpectedException .

When using @Test(expected=SomeException.class) a test method will pass if the exception is thrown anywhere in the method.

When you use ExpectedException :

@Test
public void testException()
{
    // If SomeException is thrown here, the test will fail.
    expectedException.expect(SomeException.class);
    // If SomeException is thrown here, the test will pass.
}

You can also test:

  • an expected message: ExpectedException.expectMessage() ;
  • an expected cause: expectedException.expectCause() .

As a side note: I don't think using enums for exception messages/causes is good practice. (Please correct me if I'm wrong.)

In your special case, you want to test (1) if the expected exception type is thrown and (2) if the error number is correct, because the method can thrown the same exception with different types.

This requires an inspection of the exception object. But, you can stick to the recommendation and verify that the right exception has been thrown:

@Test(expected = OurCustomException.class)
public void testDoSomethingFailsBecauseZzz() {
   try {
      doSomething();
   } catch (OurCustomException e) {
      if (e.getFailureEnum.equals(FailureEnum.ZZZ))  // use *your* method here
         throw e;

      fail("Catched OurCostomException with unexpected failure number: " 
        + e.getFailureEnum().getValue());  // again: your enum method here
   }
}

This pattern will eat the unexpected exception and make the test fail.

Edit

Changed it because I missed the obvious: we can make a test case fail and capture a message. So now: the test passes, if the expected exception with the expected error code is thrown. If the test fails because we got an unexpected error, then we can read the error code.

I made catch-exception because I was facing the same problem as you did, Stph. With catch-exception your code could look like this:

@Test
public void testDoSomethingFailsBecauseZzz() {
   verifyException(myObj, OurCustomException.class).doSomething();
   assertEquals("Omg it failed, but not like we planned", FailureEnum.ZZZ,    
               ((OurCustomException)caughtException()).getFailure() ;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM