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.
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));
}