6

我正在尝试为此编写一个单元测试:

try (final DatagramChannel channel = helper.createChannel()) {

...

}

在我的测试中,我模拟了助手(使用 Mockito),并告诉 helper.createChannel() 返回一个模拟通道。

这个测试失败了

java.lang.NullPointerException
at java.nio.channels.spi.AbstractInterruptibleChannel.close(AbstractInterruptibleChannel.java:111)

我知道 Java 中的 try-with-resources 工具在退出 try 块时调用 DatagramChannel 中的 close() 方法,但不应该调用模拟 DatagramChannel 中的 close() 方法吗?

调试器告诉我 AbstractInterruptibleChannel 中的 closeLock 为空。

我应该继承 DatagramChannel,覆盖其中的 close() 方法,然后模拟我的子类吗?或者,我是否以更深刻的方式做错了什么(助手模拟返回一个模拟)?

问候, Fredrik Israelsson

测试代码,根据要求:

@Mock
private InetAddress peerAddress;
@Mock
private UDPChannelHelper helper;
@Mock
private DatagramChannel channel;

private UDPTransportImpl transport;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    when(helper.createChannel()).thenReturn(channel);
    transport = new UDPTransportImpl(peerAddress, 0, helper);
}

@Test
public void testNormalSubmit() throws Exception {
    transport.submit("Hello");
}

如您所见,我没有为 channel.close() 指定任何行为。我相信我不应该这样做,因为 close() 返回无效。

4

3 回答 3

8

你在嘲笑一个真正的类DatagramChannel,扩展AbstractInterruptibleChannel。然而,这AbstractInterruptibleChannel.close是最终的,Mockito 目前无法模拟最终代码。这就解释了为什么代码中有 NPE。

我必须提醒您,人们普遍认为,模拟您不拥有的类型是不好的做法。我见过人们这样做,几年后当真正的实现发生变化时,他们感到很惊讶,但模拟行为没有改变,所以当他们更新库的版本时,他们错误地认为一切都很好。

尽管如此,如果您想继续这种方式,因为您有正当理由(并且有一些),您可以返回一个模拟接口,就像Channel实际 extends一样Closeable。或者,您可以使用您需要与之交互的任何其他界面,这些界面存在于DatagramChannel. 此外,如果您需要多个界面,只需使用mock(Channel.class, withSetting().extraInterfaces(...)).

希望能帮助干杯,布莱斯

于 2013-02-28T13:36:24.517 回答
2

我遇到了同样的问题,使用 spy(..) 而不是 mock(..) 对我有用。我试图在截断文件时模拟错误,并且我的系统正在相应地处理错误。

FileChannel fileChannel = spy(FileChannel.class);
mockStatic(FileChannel.class);
when(FileChannel.open(eq(filePath), eq(StandardOpenOption.WRITE))).thenReturn(fileChannel);
when(fileChannel.truncate(1000L)).thenThrow(new IOException("Unable to truncate file"));

...

// Snippet being tested!
fileChannel = FileChannel.open(filePath, StandardOpenOption.WRITE);
fileChannel.truncate(1000L); // Will throw the exception!
于 2017-11-21T22:00:07.607 回答
1

撇开您是否应该这样做不谈,解决此问题的一种方法是通过为closeLock用于同步千钧一发。

private static void fixChannelMock(AbstractInterruptibleChannel mockFileChannel) throws Exception {
    Field closeLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock");
    closeLockField.setAccessible(true);
    closeLockField.set(mockFileChannel, new Object());
}

尽管 AbstractInterruptibleChannel 的内部实现可能会发生变化,但请准备好在较小的 Java 版本上修复上述代码。

于 2015-02-10T05:18:59.523 回答