在JUnit中覆盖Java函数接口

mpbci0fu  于 2023-04-19  发布在  Java
关注(0)|答案(2)|浏览(107)

我正在使用JUnit和Mockito编写单元测试。我试图测试可运行块内部的逻辑。当捕获的参数不是流时,使用参数captor可以正常工作,但当参数是流时,错误为java.lang.IllegalStateException: source already consumed or closed。我知道一旦try-catch块完成,流将被关闭,但我们如何测试可运行块内部的逻辑?
下面是要测试的bean的逻辑:

...

@Transactional
public void execute() {
   try (Stream<Object> data = jpaRepository.fetchStream()) {
      Stream<Runnable> tasks = data.map(t -> () -> {
         // logic to be tested goes here
      });
      
      someBean.method(tasks);
   }
}

下面是单元测试

...

@InjectMocks
private App app;

@Mock
private SomeBean someBean;

@Captor
private ArgumentCaptor<Stream<Runable>> argumentCaptor;

@Test
void test() {
   app.execute();

   verify(someBean).method(argumentCaptor.capture());

   argumentCaptor.getValue().findFirst().get().run(); // Here the error thrown: java.lang.IllegalStateException: source already consumed or closed
}
khbbv19g

khbbv19g1#

生成的Stream是惰性的,所以你必须确保它在someBean.method()返回之前被消耗掉。解决方案取决于你的需要:
最简单的解决方案是向bean传递一些不懒惰的东西,比如一个列表,只要在传递之前收集你的流就可以了。
如果这不是一个选项,你必须确保测试bean在方法被调用时立即进行收集,这应该可以通过子类或自定义模拟实现来实现。

jdgnovmf

jdgnovmf2#

为了简化问题,看一下这个例子。我们将流Map到流,然后将其存储在对象中。(这里是StreamHolder的情况,在您的情况下是ArgumentCaptor)。
正如你所看到的,流被存储在那里,然后关闭(当try with resources关闭时)。在那之后,如果我们尝试访问流的元素,我们将得到你提到的IllegalStateException

@Test
void test() {
    StreamHolder holder = new StreamHolder();

    try(Stream<Integer> ids = Stream.of(1,2,3,4,5,6,7)) {
        Stream<Runnable> tasks = ids
                .map(id -> () -> System.out.println("processing id "+ id));
        holder.setStream(tasks);
    }

    holder.getStream().findFirst();
    // java.lang.IllegalStateException: source already consumed or closed
}

@Data
class StreamHolder {
    Stream<Runnable> stream;
}

一个解决方案是不使用流来传递/存储数据。流主要是为了处理它们的元素。
因此,首先收集它们并传递Collection this method将更安全,并且它也会修复测试。

List<Runnable> tasks = data.map(t -> () -> {
      // logic to be tested goes here
   })
   .toList();

someBean.method(tasks);

相关问题