Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 336
Henry Avatar asked Oct 15 '18 21:10

Henry


People also ask

How to assert an exception is thrown in JUnit?

JUnit Test Exception Examples - How to assert an exception is thrown 1 Test Exception in JUnit 5 - using assertThrows () method#N#JUnit 5 provides the assertThrows () method that asserts a... 2 Test Exception in JUnit 4#N#In JUnit 4.7 or above, you can test exception by using the @Rule annotation with an... 3 Test Exception in JUnit 3 More ...

What is new in the JUnit 5 version of testing?

JUnit 5 introduced a new way of testing for expected exceptions, which is an improvement from what was available for the purpose in previous versions. Maybe with JUnit 4 you preferred the good old-fashioned Try-Catch with fail (). I did, too. That has its drawbacks, as I will show in a little bit.

What is assertthrows In JUnit 5?

1. Syntax of Junit 5 assertThrows() It asserts that execution of the supplied executable throws an exception of the expectedType and returns the exception. If no exception is thrown, or if an exception of a different type is thrown, this method will fail.

What is numberformatexception In JUnit 5?

JUnit 5 expected exception example The given below is a very simple example of a test that expects NumberFormatException to be thrown when the supplied code block is executed. Here executable code is Integer.parseInt ("One") which throws NumberFormatException if method argument is not a valid numeric number.


Video Answer


1 Answers

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); } 
like image 166
NPras Avatar answered Sep 27 '22 19:09

NPras