Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I mock an auto closeable resource properly?

Tags:

java

mockito

I am trying to write a unit test for this:

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

...

}

In my test, I mock the helper (using Mockito), and tell helper.createChannel() to return a mocked channel.

This test fails with

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

I understand that the try-with-resources facility in Java calls the close() method in the DatagramChannel upon exiting the try block, but shouldn't the close() method in the mocked DatagramChannel be called upon?

The debugger tells me that the closeLock in AbstractInterruptibleChannel is null.

Should I subclass the DatagramChannel, override the close() method in it, and then mock my subclass instead? Or, am I doing something wrong in a more profound way (the helper mock returns a mock)?

Regards, Fredrik Israelsson

Test code, upon request:

@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");
}

As you can see, I do not specify any behavior for channel.close(). I am under the belief that I should not have to, because close() returns void.

like image 392
Fredrik Israelsson Avatar asked Feb 28 '13 08:02

Fredrik Israelsson


People also ask

What do you mean by auto closeable resources?

public interface AutoCloseable. An object that may hold resources (such as file or socket handles) until it is closed. The close() method of an AutoCloseable object is called automatically when exiting a try -with-resources block for which the object has been declared in the resource specification header.

How do you try-with-resources?

The try -with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try -with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.

Which of the following is the correct syntax of try-with-resources?

The try-with-resources statement automatically closes all the resources at the end of the statement. A resource is an object to be closed at the end of the program. As seen from the above syntax, we declare the try-with-resources statement by, declaring and instantiating the resource within the try clause.

What is difference between closeable and AutoCloseable?

Closeable extends IOException whereas AutoCloseable extends Exception. Closeable interface is idempotent (calling close() method more than once does not have any side effects) whereas AutoCloseable does not provide this feature. AutoCloseable was specially introduced to work with try-with-resources statements.


3 Answers

You are mocking a real class DatagramChannel, that extends AbstractInterruptibleChannel. However the AbstractInterruptibleChannel.close is final, and Mockito cannot currently mock final code. Which explains why you have an NPE in the code.

I must remind you that it is commonly accepted that mocking types you don't own is bad practice. I've seen people do it and they had bad surprises years later when the real implementation had changed, but the mock behavior didn't, so they wrongly thought everything was alright when they updated the version of the libraries.

Still if you want to continue this way because you have valid reasons for that (and there are some), you could return instead a mock of an interface, like Channel that actually extends Closeable. Or you can use any other interface that you need to interact with that were present in DatagramChannel. Also if you need more than one interface just use mock(Channel.class, withSetting().extraInterfaces(...)).

Hope that helps Cheers, Brice

like image 153
Brice Avatar answered Nov 10 '22 18:11

Brice


I had the same problem and using spy(..) instead of mock(..) has worked for me. I was trying to simulate an error while truncating a file and my system was handling the error accordingly.

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!
like image 28
Felipe Desiderati Avatar answered Nov 10 '22 16:11

Felipe Desiderati


Keeping aside whether you should be doing this or not, one way you can work around this issue is by "fixing" the AbstractInterruptibleChannel mock instance (whether a FileChannel, a DatagramChannel, etc.) by providing an Object for the closeLock field used to synchronize the close call.

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

Be prepared to have to fix the above code on minor Java releases though as the internal implementation of AbstractInterruptibleChannel might change.

like image 27
bashflyng Avatar answered Nov 10 '22 17:11

bashflyng