使用并行流时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
相关产品推荐
相关产品推荐

