Search code examples
javaunit-testingjunitmockingmockito

Unit test IOException with Java, JUnit and Mockito


I'm having problems testing/mocking an IOException when using a Reader.

Code:

public class Loader {
    public static void load(Reader reader) {
        try (BufferedReader bufferedReader = new BufferedReader(reader)) {
            bufferedReader.lines();
        } catch (IOException e) {
            throw new RuntimeException("", e);
        }
    }
}

@Test
void testLoadEnvironmentVariablesWithIOException() throws IOException {
    BufferedReader bufferedReader = mock();

    when(bufferedReader.readLine()).thenThrow(new IOException(""));

    assertThrows(RuntimeException.class, () -> Loader.load(bufferedReader));
}

I tried everything - Reader, BufferedReader, lines(), read(), read(char[]), read(any()), etc., but nothing seems to work.

I either can't throw an IOException in the method, because that specific method doesn't throw this type of exception it would for example throw an UncheckedIOException, or the test just gets stuck in an infinite loop and never even finishes during runtime.

But in this example it does work (using com.opencsv.CSVReader):

public class Loader {
    public static void load(Reader reader) {
        try (CSVReader csvReader = new CSVReader(reader)) {
            csvReader.readAll().stream();
        } catch (IOException | CsvException e) {
            throw new IllegalArgumentException("", e);
        }
    }
}

@Test
void testLoadWithIOException() throws IOException {
    BufferedReader reader = mock();
 
    when(reader.readLine()).thenThrow(new IOException(""));
 
    assertThrows(IllegalArgumentException.class, () -> BookLoader.load(reader));
}

It's probably the way readers wrap each other and how they use the inner reader, but I spent way too long looking and the source code and couldn't find a method to mock to make the test run properly.

I also don't want to use mockStatic or Java Reflection, because clearly there is an easier and simpler way with another type of reader wrapper, but I just can't seem to find a way to make it work in the first example.


Solution

  • While lines does internally calls readLine, it doesn't throw any IOException caused by it upwards to the calling code, but instead catches it internally and instead throws an UncheckedIOException (as you mentioned). Thus, as you noted, mocking the reader's readLine to throw an IOException doesn't achieve the behavior you want.

    In fact, if you look at the method's declaration, there just is no way for lines to throw an IOExctpion - it's a checked exception, and this method doesn't declare it.

    The IOExcetpion in that snippet comes from the close method that's implicitly called by the try-with-resource syntax.

    Once that's understood, the solution is simple - mock close to throw an IOException:

    @Test
    void testLoadEnvironmentVariablesWithIOException() throws IOException {
        BufferedReader bufferedReader = mock();
    
        doThrow(new IOException("")).when(bufferedReader).close(); // Here!
        assertThrows(RuntimeException.class, () -> Loader.load(bufferedReader));
    }