简体   繁体   中英

Understanding Exception tests in JUnit 5

I've been away from Java for a while and JUnit 5 has come along in the meantime. I'm trying to rewrite some existing JUnit 4 tests as JUnit 5 but I'm struggling with how to handle methods that throw exceptions. For example, I have a class I wrote called LocalizationUtils (not to be confused with any other class by that name found in the API) and it has a method called getResources() which wants a non-null base name as an input and returns the corresponding resource bundle if it can be found. It throws a NullPointerException if the base name is null and a MissingResourceException if the resource bundle can't be located. Here's the code:

    static public ResourceBundle getResources(String baseName) throws NullPointerException, MissingResourceException {

    if (baseName == null) {
        final String msg = "The base name cannot be null.";
        NullPointerException npExcp = new NullPointerException(msg); 
        Logger logger = Logger.getLogger(CLASS_NAME);
        logger.log(Level.SEVERE, msg, npExcp);
        throw npExcp;
        }

    /* Get the resource bundle for the current locale. */
    try {
        return(ResourceBundle.getBundle(baseName));
        } 
    catch (MissingResourceException mrExcp) {
        String msg = "Unable to find resources for base name " + baseName + "."; 
        Logger logger = Logger.getLogger(CLASS_NAME);
        logger.log(Level.SEVERE, msg, mrExcp); 
        throw mrExcp;
        }

}   

The manual for JUnit 5 gives this example of handling an exception:

@Test
void exceptionTesting() {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}

This example makes no sense to me. It seems to be creating an exception with the message "a message" out of thin air. I don't see that it is actually executing ANY class or method at all. There's no real explanation of how this is supposed to work so maybe/probably it's just a misunderstanding on my part.

In an attempt to write a test that actually executes my code and forces both errors to occur, I wrote this JUnit 5 test:

@Test
void testGetResourcesString() {

    //Normal cases

    //Exception - input parameter is null
    /* Verify that the expected type of exception was thrown. */
    Throwable npExcp = assertThrows(NullPointerException.class, () -> {
        LocalizationUtils.getResources(null);
    });   
    /* Verify that the exact right error message was generated. */   
    assertEquals("The base name cannot be null.", npExcp.getMessage()); 


    //Exception - all input is non-null but resource bundle not found
    /* Verify that the expected type of exception was thrown. */
    Throwable mrExcp = assertThrows(MissingResourceException.class, () -> {
        LocalizationUtils.getResources("foo");
    });   
    /* Verify that the exact right error message was generated. */
    assertEquals("Can't find bundle for base name foo, locale en_CA", mrExcp.getMessage());     
}

Everything in this test works as I would expect and it seems more logical than the example in the manual since it actually executes a class and method to cause the exception to be thrown. I also wanted to be sure that exactly the right message was generated since it's easy to imagine a method with several input parameters instead of just one where any of the parameters being null should throw an exception with a unique message. Simply determining that the right exception was thrown doesn't seem like enough to me: I feel that I want to be sure that the right message was created so that I know the code is reacting to the right null parameter out of several.

I'm reluctant to believe the manual is wrong though; I imagine a team of several experienced developers wrote it and were very careful. Can anyone with more JUnit 5 knowledge than me - which must be just about everyone who has ever used JUnit 5 - confirm that the example in the manual is correct and, if it is, how it is supposed to work when no class or method name is provided?

The example does call a "method". The lambda expression () -> {...} defines an anonymous method . It contains one statement: throw new IllegalArgumentException .

Refer to the java language specs

() -> {}                // No parameters; result is void
() -> 42                // No parameters, expression body
() -> null              // No parameters, expression body
() -> { return 42; }    // No parameters, block body with return
() -> { System.gc(); }  // No parameters, void block body
(int x) -> x+1              // Single declared-type parameter
(int x) -> { return x+1; }  // Single declared-type parameter
(x) -> x+1                  // Single inferred-type parameter
 x  -> x+1                  // Parens optional for single inferred-type case

So the example has one method, and all it does is throw an exception.

() -> { throw new IllegalArgumentException("a message"); }

And in your code, you're actually defining one method with no parameters, calling your method with one parameter.

() -> { LocalizationUtils.getResources(null); } 

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