1

I have the following setup:

public interface CommandRunner
{
    void run(String cmd, InputStream is) throws IOException;
}

public class CommandRunnerInputFile
{
  private final CommandRunner commandRunner;
  
  public CommandRunnerInputFile(CommandRunner commandRunner) {
    this.commandRunner = commandRunner;
  }
  
  public void run(String command, File inputFile) throws IOException {
    try (FileInputStream is = new FileInputStream(inputFile)) {
      this.commandRunner.run(command, is);
    }
  }

}

@ExtendWith(MockitoExtension.class)
public class TestCommandRunnerInputFile
{
  @Mock CommandRunner commandRunner;
  
  @Captor ArgumentCaptor<InputStream> inputStream;

  
  private CommandRunnerInputFile commandRunnerInputFile;

  @BeforeEach
  void initService() {
    commandRunnerInputFile = new CommandRunnerInputFile(commandRunner);
  }
  
  @Test
  public void testHappyPath() throws IOException {
    ClassLoader classLoader = this.getClass().getClassLoader();
    File file = new File(classLoader.getResource("MyTestInputFile.txt").getFile());

    commandRunnerInputFile .run("MyApplication.exe", file);
    
    verify(commandRunner).run(eq("MyApplication.exe"), inputStream.capture());
    assertEquals('c', (char)inputStream.getValue().read());
  }
}

When I run this test, it fails with the following Exception:

java.io.IOException: Stream Closed
    at java.io.FileInputStream.read0(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:207)

This make sense to me since, during the execution of the method, the underlying FileInputStream is closed when it has completed execution. That is to say, at the point in time that the FileInputStream is captured by Mockito, it is open, but by the time I verify that it was passed (and attempt to verify its contents) it is closed. What can I do not to simply capture the InputStream object itself, but actually capture its contents for verification?

davidemm
  • 2,001
  • 1
  • 23
  • 31

1 Answers1

0

An ArgumentCaptor will not help here, as the Stream will most probably be closed already, e.g. from try-with-resources. So you will have to catch the content during the test with a mock:

final StringBuilder actualContent = new StringBuilder();
when(myMock.doSomethingWithAnInputStream(any()).thenAnswer(invocation -> {
    final InputStream is = invocation.getArgument(0); 
    actualContent.append(CharStreams.toString(new InputStreamReader(is, StandardCharsets.UTF_8)));
    return "foo";
  });

// ... run your tests ...

verify(myMock).doSomethingWithAnInputStream(any()); // can be omitted if only one arg
assertEquals("this should be in the stream", actualContent.toString());

As your method returns a void, the mocking looks a bit different:

doAnswer(invocation -> {
    final InputStream is = invocation.getArgument(1); 
    actualContent.append(CharStreams.toString(new InputStreamReader(is, StandardCharsets.UTF_8)));
    return null;
  }).when(commandRunner).run(anyString(), any());

I used Guava to get the InputStream content, but you can also use Apache IOUtils or one of the many other options described here.

oliver_t
  • 968
  • 3
  • 10