如何正确模拟自动关闭资源?

时间:2013-02-28 08:46:59

标签: java mockito

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

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为null。

我应该继承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()返回void。

3 个答案:

答案 0 :(得分:7)

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

我必须提醒你,人们普遍认为你不拥有的嘲弄类型是不好的做法。我已经看到人们这样做了,多年后当真正的实现发生了变化时他们就有了不好的惊喜,但模拟行为却没有,所以当他们更新库的版本时,他们错误地认为一切都没问题。

如果你想继续这种方式,因为你有充分的理由(并且有一些),你可以返回一个界面的模拟,比如实际扩展Channel的{​​{1}}。或者,您可以使用Closeable中存在的与您需要进行交互的任何其他界面。此外,如果您需要多个界面,请使用DatagramChannel

希望有所帮助 干杯, 布莱斯

答案 1 :(得分:2)

我有同样的问题,使用间​​谍(..)而不是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!

答案 2 :(得分:1)

不管你是否应该这样做,你可以解决这个问题的一种方法是通过为{{提供一个Object'来“修复”AbstractInterruptibleChannel模拟实例(无论是FileChannel,DatagramChannel等)。 1}}用于同步近距离呼叫的字段。

closeLock

准备好必须在次要Java版本上修复上述代码,因为AbstractInterruptibleChannel的内部实现可能会发生变化。