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

如何Mock JAX-RS中的StreamingOutput?视频流方法测试求助

没问题,我来帮你搞定这个Mock测试的事儿~你的核心需求是要Mock掉真实的HTTP请求,同时验证视频流的拷贝逻辑对吧?下面给你两种可行的方案,从快速测试到更优雅的可测试设计都有:

核心思路

你的videoAsStream方法里,真正需要Mock的是从URL获取输入流这一步——毕竟测试时不能真的去请求外部视频地址。我们要避免直接Mock整个StreamingOutput,那样没法验证内部的IO拷贝和资源关闭逻辑。


方案一:直接Mock URL与URLConnection(快速测试方案)

因为URL是final类,默认Mockito没法Mock,所以得先开启Mockito的Mock Final特性:

  1. 在src/test/resources/mockito-extensions/目录下创建文件org.mockito.plugins.MockMaker,内容写mock-maker-inline
  2. 确保用的是Mockito 2.10+版本

接下来是测试代码,我们会Mock URL的静态构造方法、URLConnection以及输入流:

@ExtendWith(MockitoExtension.class)
public class VideoServiceTest {

    @Mock
    private URL mockUrl;
    @Mock
    private URLConnection mockUrlConnection;
    @Mock
    private InputStream mockInputStream;

    // 假设你的业务类叫VideoService
    private VideoService videoService;

    @BeforeEach
    void setUp() {
        videoService = new VideoService();
    }

    @Test
    void videoAsStream_shouldCopyInputStreamToOutput() throws Exception {
        String testVideoUrl = "http://test.com/video.mp4";
        byte[] testVideoData = "sample video bytes".getBytes();

        // Mock URL的静态构造方法,返回我们的Mock实例
        try (MockedStatic<URL> mockedUrl = mockStatic(URL.class)) {
            mockedUrl.when(() -> new URL(testVideoUrl)).thenReturn(mockUrl);
            when(mockUrl.openConnection()).thenReturn(mockUrlConnection);
            when(mockUrlConnection.getInputStream()).thenReturn(mockInputStream);

            // 模拟输入流的读取逻辑,返回测试数据后结束流
            when(mockInputStream.read(any(byte[].class))).thenAnswer(invocation -> {
                byte[] buffer = invocation.getArgument(0);
                System.arraycopy(testVideoData, 0, buffer, 0, testVideoData.length);
                return testVideoData.length;
            }).thenReturn(-1);

            // 调用目标方法
            StreamingOutput streamingOutput = videoService.videoAsStream(testVideoUrl);

            // 验证流拷贝逻辑
            ByteArrayOutputStream testOutput = new ByteArrayOutputStream();
            streamingOutput.write(testOutput);

            // 断言输出内容和测试数据一致
            assertArrayEquals(testVideoData, testOutput.toByteArray());
            // 验证输入流被正确关闭(因为用了try-with-resources)
            verify(mockInputStream).close();
            verify(mockUrlConnection).getInputStream();
        }
    }

    @Test
    void videoAsStream_shouldThrowUncheckedIOExceptionForInvalidUrl() {
        // 传入无效URL,验证异常抛出
        String invalidUrl = "invalid-url-format";
        assertThrows(UncheckedIOException.class, () -> videoService.videoAsStream(invalidUrl));
    }
}

方案二:抽象依赖(推荐的可测试设计)

直接Mock URL的方式有点依赖Mockito的特殊特性,而且代码耦合度高。更优雅的方式是把“获取视频输入流”的逻辑抽象成接口,让业务类依赖接口而非具体实现:

第一步:定义抽象接口

public interface VideoStreamProvider {
    InputStream getVideoInputStream(String videoUrl) throws IOException;
}

第二步:重构业务类,注入接口

public class VideoService {
    private final VideoStreamProvider streamProvider;

    // 构造注入,方便测试时传入Mock实现
    public VideoService(VideoStreamProvider streamProvider) {
        this.streamProvider = streamProvider;
    }

    public StreamingOutput videoAsStream(final String videoUrl) {
        try {
            return output -> {
                try(final InputStream inputStream = streamProvider.getVideoInputStream(videoUrl)){
                    IOUtils.copy(inputStream, output);
                    output.close();
                }
            };
        } catch (final IOException e) {
            log.error("Url exception for url {}", videoUrl);
            throw new UncheckedIOException(e);
        }
    }
}

第三步:编写测试代码

这时候测试就非常简单了,直接Mock抽象接口即可:

@ExtendWith(MockitoExtension.class)
public class VideoServiceTest {

    @Mock
    private VideoStreamProvider mockStreamProvider;
    @Mock
    private InputStream mockInputStream;

    private VideoService videoService;

    @BeforeEach
    void setUp() {
        videoService = new VideoService(mockStreamProvider);
    }

    @Test
    void videoAsStream_shouldCopyInputStreamToOutput() throws Exception {
        String testVideoUrl = "http://test.com/video.mp4";
        byte[] testVideoData = "sample video content".getBytes();

        // 模拟输入流返回测试数据
        when(mockInputStream.read(any(byte[].class))).thenAnswer(invocation -> {
            byte[] buffer = invocation.getArgument(0);
            System.arraycopy(testVideoData, 0, buffer, 0, testVideoData.length);
            return testVideoData.length;
        }).thenReturn(-1);

        when(mockStreamProvider.getVideoInputStream(testVideoUrl)).thenReturn(mockInputStream);

        // 调用方法
        StreamingOutput streamingOutput = videoService.videoAsStream(testVideoUrl);

        // 验证输出
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        streamingOutput.write(outputStream);

        assertArrayEquals(testVideoData, outputStream.toByteArray());
        verify(mockInputStream).close();
        verify(mockStreamProvider).getVideoInputStream(testVideoUrl);
    }

    @Test
    void videoAsStream_shouldThrowUncheckedIOExceptionWhenStreamFails() throws Exception {
        String testVideoUrl = "http://test.com/video.mp4";
        when(mockStreamProvider.getVideoInputStream(testVideoUrl))
                .thenThrow(new IOException("Connection failed"));

        assertThrows(UncheckedIOException.class, () -> videoService.videoAsStream(testVideoUrl));
        verify(mockStreamProvider).getVideoInputStream(testVideoUrl);
    }
}

额外注意点

  • 一定要验证try-with-resources是否正确关闭了输入流,也就是验证mockInputStream.close()被调用,避免资源泄漏。
  • 如果要测试边界情况(比如空流、大文件流),可以调整Mock输入流的返回逻辑来模拟。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:34:09