Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I want to mock a proprietary class that extends InputStream, mock read, verify close

I want to use Mockito to mock AmazonS3 and test opening a stream from it and then verifying that the stream is closed after my code has read from it. I'd also like to get the bytes read from the stream. Something like this:

AmazonS3 client = mock(AmazonS3.class);
when(tm.getAmazonS3Client()).thenReturn(client);
S3Object response = mock(S3Object.class); 
when(client.getObject(any(GetObjectRequest.class))).thenReturn(response);
S3ObjectInputStream stream = mock(S3ObjectInputStream.class); 
when(response.getObjectContent()).thenReturn(stream);

somehow mock the read method

MyObject me = new MyObject(client);
byte[] bra me.getBytes(File f, offset, length);
assertEquals(length, bra.length);
verify(stream).close();
like image 248
Karl K Avatar asked Oct 24 '25 19:10

Karl K


2 Answers

You can use Mockito's Answer to mock the stream.

    String expectedContents = "Some contents";
    InputStream testInputStream = new StringInputStream(expectedContents);
    S3ObjectInputStream s3ObjectInputStream = mock(S3ObjectInputStream.class);
    S3Object s3Object = mock(S3Object.class);
    AmazonS3Client amazonS3Client = mock(AmazonS3Client.class);
    S3AttachmentsService service = new S3AttachmentsService(amazonS3Client);

    when(s3ObjectInputStream.read(any(byte[].class))).thenAnswer(invocation -> {
        return testInputStream.read(invocation.getArgument(0));
    });

I have a more extensive example here. Hope that helps.

like image 93
CodingNagger Avatar answered Oct 26 '25 10:10

CodingNagger


You could probably get this to work in a simple way:

when(stream.read()).thenReturn(0, 1, 2, 3 /* ... */);

That said, right now, you're mocking Amazon's implementation. This means that if any of the methods turn final, you'll be in bad shape, because Mockito doesn't support mocking final methods due to compiler constraints. Mocking types you don't own is tempting, but can lead to breakage.

If your goal is to test that getBytes returns the right value and closes its stream, a more stable approach may be to refactor to use an arbitrary InputStream:

class MyObject {
  public byte[] getBytes(File f, int offset, int length) {
    /* ... */

    // Delegate the actual call to a getBytes method.
    return getBytes(s3ObjectInputStream, f, offset, length);
  }

  /** Call this package-private delegate in tests with any arbitrary stream. */
  static byte[] getBytes(InputStream s, File f, int offset, int length) {
    /* ... */
  }
}

At that point, you can test it using a spy(new ByteArrayInputStream(YOUR_BYTE_ARRAY)) and get a very compelling test—complete with a call to verify(stream).close().

Along those lines, another solution is to add a seam you can control, effectively wrapping getBytes from afar:

class MyObject {
  public byte[] getBytes(File f, int offset, int length) {
    /* ... */
    InputStream inputStream = getStream(response.getObjectContent());
    /* ... */
  }

  /** By default, just pass in the stream you already have. */
  InputStream getStream(S3ObjectInputStream s3Stream) {
    return s3Stream;
  }
}

class MyObjectTest {
  @Test public void yourTest() {
    /* ... */
    MyObject myObject = new MyObject(client) {
      /** Instead of returning the S3 stream, insert your own. */
      @Override InputStream getStream() { return yourMockStream; }
    }
    /* ... */
  }
}

Remember, though, that you're testing the way you think Amazon S3 should work, not whether it continues to work in practice. If your goal is to "test opening a stream from [S3]", an integration test that runs against an actual S3 instance is probably a good idea, to cover the gap between your S3 mock and S3 in reality.

like image 27
Jeff Bowman Avatar answered Oct 26 '25 09:10

Jeff Bowman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!