Search code examples
javaspring-bootunit-testingjunit

JUnit Test for expected exception fails despite throwing the same exception


I've scoured through StackOverflow for the same types of questions, and checked each response, but still facing this problem. I am testing for a IOException to be thrown from a method called readStream(), and the method does throw the IOException, but Junit fails the test with an AssertionException.

private static StringBuilder readStream(InputStream inputStream) throws IOException {

    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    StringBuilder stringBuilder = new StringBuilder();

    String line;

    try {
        while ((line = reader.readLine()) != null){
            stringBuilder.append(line + "\n");
        }
    } catch (IOException e) {
        e.printStackTrace();
        throw e;
    }
   finally {
        try{
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return stringBuilder;
}

Here is my unit test:

@SpringBootTest(classes = RequestResponseUtility.class)
public class RequestResponseUtilityTest extends AbstractTest {

    @Mock
    private InputStream inputStream;

    @Test(expected = IOException.class)
    public void convertStreamToStringNullTest() throws IOException {

        RequestResponseUtility.convertStreamToString(inputStream);
    }
}

I've tried, as you see, to have the exception in the original class actually throw the exception, rather than just catch and print the stack trace:

    e.printStackTrace();
    throw e;

I've also tried both using the annotation @Test(expected=IOException.class) and using the @Rule approach. Both fail the test. I've tried annotating my test with both @SpringBootTest (as you see above) and with @RunWith(springjunit4classrunner.class). I'm really at a loss here. I get this error

java.lang.AssertionError: Expected exception: java.io.IOException

But my stack trace is

java.io.IOException: Underlying input stream returned zero bytes
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:288)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at main.java.utilities.RequestResponseUtility.readStream(RequestResponseUtility.java:25)
    at main.java.utilities.RequestResponseUtility.convertStreamToString(RequestResponseUtility.java:45)

This is breaking my heart. It's not a huge deal and doesn't impact the performance of my code in production, but I want to understand why it's actually happening.


Solution

  • Well, I feel kind of silly because I just solved my own question right after I posted this. I didn't examine the stack trace closely enough:

    at main.java.utilities.RequestResponseUtility.readStream(RequestResponseUtility.java:25)
            at main.java.utilities.RequestResponseUtility.convertStreamToString(RequestResponseUtility.java:45)
            at test.java.utilities.RequestResponseUtilityTest.convertStreamToStringNullTest(RequestResponseUtilityTest.java:42)
    

    The first method that is called is convertStreamToStringNullTest(), my unit test. This calls convertStreamToString(). However, convertStreamToString() actually calls a private helper method inside my RequestResponseUtility class called readStream(). In my question, I had posted the code to readStream(), since I knew intuitively that was where the exception was being thrown.

    However, as the exception propagates back down the stack to convertStreamToString(), I never throw the exception. I only print the stack trace. So it gets stuck at that method, and therefore the unit test, which is "listening" for a IOException from convertStreamToString(), never gets the thrown exception!

    Moral of the story is to check for nested methods and really trace where your exception should be coming from!