javamultithreadingmockitojava-stream

Java Mocking Issue When Using Parallel Streams


I have the following ImageService:

public class ImageService {

public ImageService() {
}

public List<BufferedImage> getBufferedImages(List<byte[]> imageBytesArrays) {
    return imageBytesArrays.parallelStream()
            .map(bytes -> {
                try {
                    return getBufferedImageOrThrow(bytes);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            })
            .collect(Collectors.toList());
}

public BufferedImage getBufferedImageOrThrow(byte[] imageAsByteArray) throws IOException {
    try (ByteArrayInputStream imageAsInputStream = new ByteArrayInputStream(imageAsByteArray)) {
        BufferedImage bufferedImage = ImageIO.read(imageAsInputStream);

        if (bufferedImage == null) {
            throw new IOException("Failed to create BufferedImage object from image");
        }

        return bufferedImage;
    }
}

Below is the unit test:

@ExtendWith(MockitoExtension.class)
public class ImageServiceTest {

private static final Object lock = new Object();

@Test
void shouldHandleImagesWithParallelStream() {
    try (MockedStatic<ImageIO> imageIOMockedStatic = mockStatic(ImageIO.class)) {
        imageIOMockedStatic
                .when(() -> ImageIO.read(any(ByteArrayInputStream.class)))
                .thenAnswer(invocation -> {
                    synchronized (lock) {
                        return new BufferedImage(10, 10, 1);
                    }
                });

        List<byte[]> imageBytesArrays = getTestBytesArrays();

        ImageService imageService = new ImageService();

        List<BufferedImage> bufferedImages = imageService.getBufferedImages(imageBytesArrays);

        bufferedImages.forEach(Assertions::assertNotNull);
    }
}

private List<byte[]> getTestBytesArrays() {
    List<byte[]> imageBytesArrays = new ArrayList<>();

    imageBytesArrays.add(new byte[]{1, 2, 3});
    imageBytesArrays.add(new byte[]{4, 5, 6});
    imageBytesArrays.add(new byte[]{7, 8, 9});
    imageBytesArrays.add(new byte[]{10, 11, 12});
    imageBytesArrays.add(new byte[]{13, 14, 15});
    imageBytesArrays.add(new byte[]{16, 17, 18});

    return imageBytesArrays;
 }
}

When I run the test, it consistently fails since ImageIO.read() always returns null. If I replace the parallel stream with a regular stream then it works, but I want to make it work with a parallel stream (or some equivalent multi-threading scheme). Thanks!


Solution

  • Easiest way to solve it is moving ImageIO.read to a separate service and mocking it.

    I suspect that the problem is caused by the fact that Thread used by parralelStream has its own class loader. mockStatic does its magic by switching classloaders and is unable to do it for a thread it doesn't know about.

    Another solution is to mock parralelStream to work in the same thread, but you will probably open another can of worms by that.