javamockitojunit5spy

How to properly spy on an input stream


My understanding is, that Mockito.spy(object) wraps a proxy around an existing object. This proxy delegates the method calls to the spied object and allows further verification (So it's different to a mock which provides no implementation).

I want to spy on an input stream to ensure the close/read methods are properly called. But the following (simple) spy code doesn't work:

// Create a spy input stream object
String testData = "Hello";
InputStream inputStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
InputStream spiedInputStream = spy(inputStream);
assertEquals(testData.getBytes(StandardCharsets.UTF_8).length, spiedInputStream.available()); // Fails: Expected 5, Actual 0

// Read the input stream
byte [] readData = new byte[testData.length()];
assertEquals(testData.getBytes(StandardCharsets.UTF_8).length, spiedInputStream.read(readData)); // Fails: Expected 5, Actual -1
assertEquals(testData, new String(readData, StandardCharsets.UTF_8)); // Fails, readData is fully zeroed

So what am I doing wrong (Ubuntu 22.04, Java 17, Mockito 4.7.0)


Solution

  • The behaviour you described is reproducible only on the following configuration:

    The simplest way for you to proceed is to switch to mockito-inline.

    In case of mockito-core and JDK 17, the fields in the spy are not properly initialized:

    public ByteArrayInputStream(byte buf[]) {
        this.buf = buf;
        this.pos = 0;
        this.count = buf.length;
    }
    

    The count variable should be equal to buf.length, but in the spy it is set to 0.

    The problem stems from the fact that subclass mock maker is fundamentally limited on JDK17, mockito team seems to be aware of the problem and even considers switching to inline mock maker as default on JDK 17:

    Switch the default mockmaker to the inline mockmaker on JDK 17+ #2589:

    TLDR: more and more use cases are broken (by default) with Mockito and JDK 17. That's because the subclass mockmaker runs into fundamental limitations on JDK 17, but the inline mockmaker works as expected.