Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do unit test for Exceptions?

Tags:

java

junit

People also ask

Should exceptions be unit tested?

Unit test cases for exceptions should improve the stability and robustness of your application. Unit Test cases can ensure of proper exception handling is implemented.

How do I test exceptions in JUnit 5?

In JUnit 5, to write the test code that is expected to throw an exception, we should use Assertions. assertThrows(). In the given test, the test code is expected to throw an exception of type ApplicationException or its subtype. Note that in JUnit 4, we needed to use @Test(expected = NullPointerException.


You can tell junit that the correct behavior is to get an exception.

In JUnit 4, it goes something like:

@Test(expected = MyExceptionClass.class) 
public void functionUnderTest() {
    …
}

Other answers have addressed the general problem of how to write a unit test that checks that an exception is thrown. But I think your question is really asking about how to get the code to throw the exception in the first place.

Take your code as an example. It would be very hard to cause your getServerName() to internally throw an exception in the context of a simple unit test. The problem is that in order for the exception to happen, the code (typically) needs to be run on a machine whose networking is broken. Arranging for that to happen in a unit test is probably impossible ... you'd need to deliberately misconfigure the machine before running the test.

So what is the answer?

  1. In some cases, the simple answer is just to take the pragmatic decision and not go for total test coverage. Your method is a good example. It should be clear from code inspection what the method actually does. Testing it is not going to prove anything (except see below **). All you are doing is improve your test counts and test coverage numbers, neither of which should be project goals.

  2. In other cases, it may be sensible to separate out the low-level code where the exception is being generated and make it a separate class. Then, to test the higher level code's handling of the exception, you can replace the class with a mock class that will throw the desired exceptions.

Here is your example given this "treatment". (This is a bit contrived ... )

public interface ILocalDetails {
    InetAddress getLocalHost() throws UnknownHostException;
    ...
}

public class LocalDetails implements ILocalDetails {
    public InetAddress getLocalHost() throws UnknownHostException {
        return InetAddress.getLocalHost();
    }
}

public class SomeClass {
    private ILocalDetails local = new LocalDetails();  // or something ...
    ...
    public String getServerName() {
        try {
            InetAddress addr = local.getLocalHost();
            return addr.getHostName();
        }
        catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
}

Now to unit test this, you create a "mock" implementation of the ILocalDetails interface whose getLocalHost() method throws the exception you want under the appropriate conditions. Then you create a unit text for SomeClass.getServerName(), arranging that the instance of SomeClass uses an instance of your "mock" class instead of the normal one. (The last bit could be done using a mocking framework, by exposing a setter for the local attribute or by using the reflection APIs.)

Obviously, you would need to modify your code to make it testable like this. And there are limits to what you can do ... for example, you now cannot create a unit test to make the real LocalDetails.getLocalHost() method to throw an exception. You need to make a case-by-case judgement as to whether it is worth the effort of doing this; i.e. does the benefit of the unit test outweigh the work (and extra code complexity) of making the class testable in this way. (The fact that there is a static method at the bottom of this is a large part of the problem.)


** There is a hypothetical point to this kind of testing. In your example, the fact that the original code catches an exception and returns an empty string could be a bug ... depending on how the method's API is specified ... and a hypothetical unit test would pick it up. However, in this case, the bug is so blatant that you would spot it while writing the unit test! And assuming that you fix bugs as you find them, the unit test becomes somewhat redundant. (You wouldn't expect someone to re-instate this particular bug ...)


Okay there are a few possible answers here.

Testing for an exception itself is easy

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

@Test
public void TestForException() {
    try {
        doSomething();
        fail();
    } catch (Exception e) {
        assertThat(e.getMessage(), is("Something bad happened"));
    }
}

Alternately, you can use the Exception Annotation to note that you expect an exception to come out.

Now, as to you specific example, Testing that something you are creating inside your method, either via new or statically as you did, when you have no way to interact with the object is tricky. You normally need to encapsulate that particular generator and then use some mocking to be able to override the behavior to generate the exception you expect.


Since this question is in community wiki I'll add a new one for completeness: You can use ExpectedException in JUnit 4

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

@Test
public void TestForException(){
    thrown.expect(SomeException.class);
    DoSomething();
}

The ExpectedException makes the thrown exception available to all test methods.

Is is also possible to test for a specific error message:

thrown.expectMessage("Error string");

or use matchers

thrown.expectMessage(startsWith("Specific start"));

This is shorter and more convenient than

public void TestForException(){
    try{
        DoSomething();
        Fail();
    }catch(Exception e) {
      Assert.That(e.msg, Is("Bad thing happened"))
    }
}

because if you forget the fail, the test can result in a false negative.


Many unit testing frameworks allow your tests to expect exceptions as part of the test. JUnit, for example, allows for this.

@Test (expected=IndexOutOfBoundsException.class) public void elementAt() {
    int[] intArray = new int[10];

    int i = intArray[20]; // Should throw IndexOutOfBoundsException
}