You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用并行流时Java Mocking遇到的问题

问题描述

我有如下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;
        }
    }
}

对应的单元测试代码:

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

运行测试时,使用并行流会因为ImageIO.read()始终返回null导致失败,但换成普通流测试正常。需要解决并行流(多线程)下的测试问题。


问题原因

Mockito的MockedStatic默认是线程局部作用域,仅对创建它的主线程生效。并行流会使用线程池中的其他线程执行任务,这些线程调用ImageIO.read()时不会触发你定义的mock行为,而是执行真实的ImageIO.read()方法——你的测试byte数组不是有效图片数据,真实方法返回null,进而抛出IOException导致测试失败。


解决方法

方法1:全局作用域的静态Mock(快速修复)

从Mockito 3.4.0版本开始,支持为MockedStatic指定全局作用域,让所有线程都能使用这个mock。修改测试代码中的mockStatic创建逻辑:

@Test
void shouldHandleImagesWithParallelStream() {
    // 使用全局作用域创建静态mock
    try (MockedStatic<ImageIO> imageIOMockedStatic = mockStatic(ImageIO.class, 
            MockedStatic.withSettings().scope(MockedStatic.Scope.GLOBAL))) {
        imageIOMockedStatic
                .when(() -> ImageIO.read(any(ByteArrayInputStream.class)))
                .thenAnswer(invocation -> new BufferedImage(10, 10, 1));

        List<byte[]> imageBytesArrays = getTestBytesArrays();
        ImageService imageService = new ImageService();
        List<BufferedImage> bufferedImages = imageService.getBufferedImages(imageBytesArrays);
        
        bufferedImages.forEach(Assertions::assertNotNull);
    }
}

注意:全局静态mock会影响同一测试类中的其他测试,建议在测试方法内使用try-with-resources确保mock及时销毁,避免测试污染。

方法2:依赖抽象解耦(推荐方案)

静态方法耦合性高,不利于测试和扩展。可以抽象出图片读取的接口,将ImageIO的依赖注入到ImageService中:

步骤1:定义图片读取接口

public interface ImageReader {
    BufferedImage read(InputStream inputStream) throws IOException;
}

步骤2:实现真实的图片读取器

public class DefaultImageReader implements ImageReader {
    @Override
    public BufferedImage read(InputStream inputStream) throws IOException {
        BufferedImage bufferedImage = ImageIO.read(inputStream);
        if (bufferedImage == null) {
            throw new IOException("Failed to create BufferedImage object from image");
        }
        return bufferedImage;
    }
}

步骤3:修改ImageService依赖接口

public class ImageService {
    private final ImageReader imageReader;

    // 构造注入,方便测试替换
    public ImageService(ImageReader imageReader) {
        this.imageReader = imageReader;
    }

    // 保留无参构造兼容原有代码
    public ImageService() {
        this(new DefaultImageReader());
    }

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

步骤4:修改单元测试,mock接口而非静态方法

@ExtendWith(MockitoExtension.class)
public class ImageServiceTest {

    @Mock
    private ImageReader mockImageReader;

    @Test
    void shouldHandleImagesWithParallelStream() throws IOException {
        // 定义mock行为
        when(mockImageReader.read(any(ByteArrayInputStream.class)))
                .thenReturn(new BufferedImage(10, 10, 1));

        List<byte[]> imageBytesArrays = getTestBytesArrays();
        ImageService imageService = new ImageService(mockImageReader);
        List<BufferedImage> bufferedImages = imageService.getBufferedImages(imageBytesArrays);
        
        bufferedImages.forEach(Assertions::assertNotNull);
        // 验证调用次数,确保并行流处理了所有元素
        verify(mockImageReader, times(imageBytesArrays.size()))
                .read(any(ByteArrayInputStream.class));
    }

    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;
    }
}

这种方式彻底解耦了静态依赖,测试不再受多线程限制,同时提升了代码的可扩展性(比如后续可以替换其他图片读取实现)。


内容的提问来源于stack exchange,提问作者Mister_L

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 14:07:31