如何Mock JAX-RS中的StreamingOutput?视频流方法测试求助
没问题,我来帮你搞定这个Mock测试的事儿~你的核心需求是要Mock掉真实的HTTP请求,同时验证视频流的拷贝逻辑对吧?下面给你两种可行的方案,从快速测试到更优雅的可测试设计都有:
核心思路
你的videoAsStream方法里,真正需要Mock的是从URL获取输入流这一步——毕竟测试时不能真的去请求外部视频地址。我们要避免直接Mock整个StreamingOutput,那样没法验证内部的IO拷贝和资源关闭逻辑。
方案一:直接Mock URL与URLConnection(快速测试方案)
因为URL是final类,默认Mockito没法Mock,所以得先开启Mockito的Mock Final特性:
- 在
src/test/resources/mockito-extensions/目录下创建文件org.mockito.plugins.MockMaker,内容写mock-maker-inline - 确保用的是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
相关产品推荐
相关产品推荐

