模拟openInputStream的读取方法

时间:2018-09-11 17:29:54

标签: java junit mockito azure-storage powermockito

我正在从openInputStream读取数据块,并想模拟它的读取方法。

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

我模拟了InputStream,但是在模拟它的read方法时遇到了困难,因为它接受缓冲区作为引用并在读取后将字节数组复制到该缓冲区。

 @Mock
private BlobInputStream inputStream;


       // Mock
        when(cloudBlobContainer.getBlobReferenceFromServer(anyString())).thenReturn(cloudBlob);
        when(cloudBlob.openInputStream()).thenReturn(inputStream);  

1 个答案:

答案 0 :(得分:1)

InputStream非常难于模拟,这不仅是因为它的三个read覆盖以及更高版本的Java版本中添加的readNBytesreadAllBytes方法。如果使用Mockito完成,则需要使所有这些方法实现与同一数据进行交互,否则您将获得一个脆弱的测试,一旦实现调用不同的InputStream方法,该测试可能会中断。如果必须使用两次测试,则最好编写“ fake”,但当Java内置ByteArrayInputStream时,则没有理由这样做:您可以构造byte[]缓冲区(或编写一个辅助方法以根据您的测试需求构造一个),然后在测试中将其替换为InputStream。 这对于那些只问有关模拟InputStream的人来说就足够了。

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

不幸的是,这没有回答有关如何为Azure的BlobInputStream专门提供测试装置的问题,这特别棘手,因为BlobInputStream has a four-arg constructor with a lot of Azure internals。好消息是,自1.9.5起,Mockito提供了一个delegatesTo method in AdditionalAnswers的文档(我非常强调)为:

  

直接将呼叫转发给委托人的答案。 委托的类型可能与模拟的类型相同或不同。如果类型不同,则需要在委托类型上找到匹配的方法,否则会引发异常。

这意味着您可以通过创建一个真正的ByteArrayInputStream并将其可重写方法委托给它来模拟BlobInputStream。不幸的是,delegatesTo位于AdditionalAnswers而不是Answers枚举上(并且无论如何,它都需要一个实例参数,而您不能在注释中提供它),因此您需要手动构建模拟:

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

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

但是,如果可能的话,这将是一个很好的机会,将您的代码分为处理Azure输入流的部分与任何InputStream一起使用的应用程序代码 。例如,如果您的代码采用BlobInputStream并运行解析或错误纠正代码,则可以分解出方法handleInputStream(InputStream)并传入您自己的ByteArrayInputStream对其进行大量测试。这样可以最大程度地减少模拟BlobInputStream的需求,或者如果您选择仅使用真实的后端将BlobInputStream处理作为集成测试进行测试,则可以完全消除它。另请参见Mocking Files in Java - Mock Contents - Mockito,其中Brice讨论了单元测试和集成测试之间的类似划分,而不是模拟java.io.File实例。