如何使用 Mockito 验证带有 ByteBuffer 参数的调用?

Posted

技术标签:

【中文标题】如何使用 Mockito 验证带有 ByteBuffer 参数的调用?【英文标题】:How can I use Mockito to verify calls with ByteBuffer arguments? 【发布时间】:2020-04-22 15:57:37 【问题描述】:

我正在尝试为使用 ByteBuffer 的库创建测试。

这里有一个简化版:

public class ByteBufferTest 
    public static class Stuff 
        public void doSomething(Target target, ByteBuffer buffer) 
            ByteBuffer slice = buffer.slice();
            slice.limit(1);
            target.accept(slice);
            target.command();
            buffer.position(1);
            target.accept(buffer);
            // do more with the buffer
            buffer.get();
        
    

    private interface Target 
        void accept(ByteBuffer slice);
        void command();
    

    @Test
    public void doSomething() 
        final Stuff stuff = new Stuff();
        final Target mockTarget = Mockito.mock(Target.class);

        stuff.doSomething(mockTarget, ByteBuffer.wrap(new byte[]1, 2, 3, 4));

        InOrder inOrder = Mockito.inOrder(mockTarget);
        inOrder.verify(mockTarget).accept(ByteBuffer.wrap(new byte[]1));
        inOrder.verify(mockTarget).command();
        inOrder.verify(mockTarget).accept(ByteBuffer.wrap(new byte[]2, 3, 4));
    

如果doSomething 方法在末尾有buffer.get();,这会正常工作。

此测试失败,因为在调用 inOrder.verify(mockTarget).accept(ByteBuffer.wrap(new byte[]2, 3, 4)); 时,传入的参数已被之后的 buffer.get() 调用修改。

有没有办法在调用时验证该缓冲区的内容?

【问题讨论】:

这并不意味着测试确实失败了;如果doSomething 方法调用buffer.get(),则生成的 ByteBuffer 将删除一个附加元素。 在调用accept 时,它包含正确的数据。 doSomething 的约定恰恰是 accept 方法是用数据的子集和数据缓冲区调用的,但 accept 在返回后并不拥有缓冲区。真正的用例要复杂一些(它实际上是解析ByteBuffer 的内容,并将其拆分为 3 部分,前数据、命令和后数据。然后它负责确保消费后数据。 为什么 target 是 mock,但 buffer 不是?这似乎很奇怪。 我处理缓冲区的主要原因不是模拟,是 ByteBuffer 非常“原始”,除了它们是可变的。您可能不会模拟 String 实例,甚至不会模拟原始包装器。所以对我来说,不模拟缓冲区是可以的。 【参考方案1】:

选项A。如果很难测试,也许实现也有问题?每个Target 都应该立即作用于这个缓冲区吗?也许我们只是在第二次调用时交出另一个切片:

target.accept(buffer.slice());

选项 B。不测试平台,只测试交互:

    @Test
    public void doSomething1() 
        final Stuff stuff = new Stuff();
        final Target mockTarget = mock(Target.class);
        final ByteBuffer buffer = mock(ByteBuffer.class);
        final ByteBuffer slice = mock(ByteBuffer.class);
        given(buffer.slice()).willReturn(slice);

        stuff.doSomething(mockTarget, buffer);

        final InOrder inOrder = Mockito.inOrder(mockTarget);
        inOrder.verify(mockTarget).accept(slice);
        inOrder.verify(mockTarget).command();
        inOrder.verify(mockTarget).accept(buffer);
    

选项 C。您自己的 ByteBuffer 测试双重实现通常会给您完全控制权,除非 Buffer/ByteBuffer/HeapByteBuffer 类层次结构不像它那样对测试不友好。

【讨论】:

感谢您的周到回复。这是性能关键代码,所以我想尽可能避免不必要的切片。总的来说,能够在调用时验证参数状态似乎应该得到 Mockito 的支持。 @Daniel 我在回答中又添加了两个想法。 您展示的代码 sn-p 不必要地与 doSomething 的实现细节耦合。 doSomething 的合约是发送数据,发送命令,发送更多数据。它没有(也不应该)指定数据是切片、传入的缓冲区还是构造函数字节缓冲区。 无论如何,我现在决定更改我的代码以重用之前的切片。 好的。是的,最后,要断言的特定交互取决于您的要求。减少这些 slice() 调用听起来像是一种改进。

以上是关于如何使用 Mockito 验证带有 ByteBuffer 参数的调用?的主要内容,如果未能解决你的问题,请参考以下文章

Mockito - 验证对象根本没有被调用

使用 Mockito 未调用 Object 参数的验证方法

使用 Mockito 的通用“any()”方法

如何使用 mockito verify() 调用两次方法来验证方法

Mockito:您如何验证某些方法调用组的组顺序?

Mockito:使用泛型参数进行验证