1

I am reading chunks of data from openInputStream and wanted to mock it's read method.

 final byte[] testFileData = { 0x74, 0x65, 0x73, 0x74, 0x20, 0x64, 0x61, 0x74, 0x61, 0x0d, 0x0a};

 CloudBlob cloudBlob = this.getCloudBlobContainer().getBlobReferenceFromServer(blobName);

    ByteArrayOutputStream blobStream = new ByteArrayOutputStream();

    try (final InputStream inputStream =  cloudBlob.openInputStream()) {

        //Read 4MB chunks of data 
        byte[] bufferToRead = new byte[4 * 1024 *1024];
        int bytesRead = inputStream.read(bufferToRead );

        while (bytesRead > 0) {
            //Add only the total number of bytes read to Bytearrayoutputstream
            blobStream.write(bufferToRead, 0, bytesRead);
            bytesRead = inputStream.read(bufferToRead);                
        }
    } `

I did mock InputStream but finding difficulties mocking it's read method because it accepts the buffer as reference and copy byte array to it after reading it.

 @Mock
private BlobInputStream inputStream;


       // Mock
        when(cloudBlobContainer.getBlobReferenceFromServer(anyString())).thenReturn(cloudBlob);
        when(cloudBlob.openInputStream()).thenReturn(inputStream);  
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
user09
  • 920
  • 2
  • 12
  • 38

1 Answers1

4

InputStream is very difficult to mock, not least because of its three read overrides as well as the readNBytes and readAllBytes methods added in later Java versions. If done with Mockito, you'll need to have all of those method implementations interact with the same data, or else you'll wind up with a fragile test that may break as soon as your implementation calls different InputStream methods. If you must use a test double, you'd be better off writing a "fake", but there's no reason to do so when Java has ByteArrayInputStream built in: You can construct your byte[] buffer (or write a helper method to construct one as your test needs) and substitute it for your InputStream in your test. This should suffice for anyone who comes to this question simply asking about mocking an InputStream in general.

final byte[] testFileData = {
    0x74, 0x65, 0x73, 0x74, 0x20, 0x64, 0x61, 0x74, 0x61, 0x0d, 0x0a};
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(testFileData);

Unfortunately, this doesn't answer the question about how to specifically provide a test fixture for Azure's BlobInputStream, which is particularly tricky because BlobInputStream has a four-arg constructor with a lot of Azure internals. The good news is that since 1.9.5 Mockito provides a delegatesTo method in AdditionalAnswers documented (with my own emphasis) as:

An answer that directly forwards the calls to the delegate. The delegate may or may not be of the same type as the mock. If the type is different, a matching method needs to be found on delegate type otherwise an exception is thrown.

That means that you can simulate a BlobInputStream by creating a real ByteArrayInputStream and delegating the overridable methods to it. Unfortunately, delegatesTo is on AdditionalAnswers and not the Answers enum (and it requires an instance parameter anyway that you can't provide in annotations), so you'll need to construct your mock manually:

BlobInputStream mockInputStream = Mockito.mock(
    BlobInputStream.class,
    AdditionalAnswers.delegatesTo(byteArrayInputStream));

when(cloudBlobContainer.getBlobReferenceFromServer(anyString()))
    .thenReturn(cloudBlob);
when(cloudBlob.openInputStream()).thenReturn(mockInputStream);

However, if possible, this would be a good opportunity to separate your code into the part that deals with Azure input streams versus your application code that works with any InputStream. If, for example, your code takes the BlobInputStream and runs parsing or error correction code, you can factor out a method handleInputStream(InputStream) and pass in your own ByteArrayInputStream to test it heavily. This minimizes your need to mock a BlobInputStream, or may eliminate it entirely if you choose to only test your BlobInputStream handling as an integration test using a real backend. See also Mocking Files in Java - Mock Contents - Mockito, where Brice discusses a similar split between unit and integration tests instead of mocking a java.io.File instance.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251