JUnit测试Runnable逻辑遇Stream已关闭异常的解决方法
解决JUnit+Mockito测试Stream时的"source already consumed or closed"异常
核心原因
被测代码中,tasks Stream是基于try-with-resources块内的data Stream生成的。当execute()方法执行完毕后,try-with-resources会自动关闭data Stream,导致下游的tasks Stream也被关闭/消费,此时再通过ArgumentCaptor捕获的Stream调用方法就会抛出异常。
解决方案一:用Mockito Answer提前保存Runnable列表
不需要直接捕获Stream,而是在someBean.method被调用时,将Stream转换为List保存,脱离原始Stream的生命周期限制:
@InjectMocks private App app; @Mock private SomeBean someBean; private List<Runnable> capturedTasks = new ArrayList<>(); @Test void test() { // 拦截someBean.method调用,将传入的Stream转为List保存 doAnswer(invocation -> { Stream<Runnable> tasksStream = invocation.getArgument(0); capturedTasks = tasksStream.collect(Collectors.toList()); return null; }).when(someBean).method(any(Stream.class)); app.execute(); // 执行捕获到的Runnable并验证业务逻辑 capturedTasks.get(0).run(); // 这里添加你的断言逻辑,比如验证某个依赖方法是否被调用 }
解决方案二:重构被测代码,抽离任务创建逻辑
如果允许修改被测代码,可以把Runnable的创建逻辑抽成独立方法,直接测试这个方法的输出:
重构后的被测类代码
@Transactional public void execute() { try (Stream<Object> data = jpaRepository.fetchStream()) { List<Object> dataList = data.collect(Collectors.toList()); List<Runnable> tasks = createTasks(dataList); someBean.method(tasks.stream()); } } // 抽离独立方法,方便测试 protected List<Runnable> createTasks(List<Object> data) { return data.stream().map(t -> () -> { // 待测试的业务逻辑 }).collect(Collectors.toList()); }
对应的测试代码
@InjectMocks private App app; @Test void testCreateTasks() { // 构造测试数据 List<Object> testData = Arrays.asList(new Object()); // 调用抽离的方法获取Runnable List<Runnable> tasks = app.createTasks(testData); // 执行并验证业务逻辑 tasks.get(0).run(); // 添加断言逻辑 }
内容的提问来源于stack exchange,提问作者Varid Vaya
相关产品推荐
相关产品推荐

