如何在Mockito中模拟CompletableFuture的完成

7

我希望模拟当CompletableFuture成功完成时调用某些代码。

我有这个类:

public class MyClassImplementRunner implements Runnable {

    private final String param1;

    public MyClassImplementRunner(String param1) {
        this.param1 = param1;
    }

    public static CompletableFuture<Void> startAsync(String param1) {

        return CompletableFuture.runAsync(
            new MyClassImplementRunner(param1)).whenComplete(
            (response, throwable) -> {
                //some code when complete
            });

        @Override
        public void run () {
            //the runnable code
        }
    }
}

在我的Junit测试中(使用Mockito和Java 8),我需要模拟这个操作。
//some code when complete 

当Future成功完成时被称为`is called`。您能提供一些关于如何实现这一点的指示吗?

有没有解决这个用例的方案? - Md Faraz
2个回答

0

将您在whenComplete中执行的代码提取到一个字段中,并提供一个构造函数来替换它。

class Runner implement Runnable {

  private final String param;
  private final BiConsumer<Void, Throwable> callback;

  public Runner(String param) {
    this.param = param;
    this.callback = this::callbackFunction;
  }

  Runner(String param, BiConsumer<Void, Throwable> callback) {
    this.param = param;
    this.callback = callback;
  }

  public void run() {
    CompletableFuture.runAsync(task).whenComplete(callback);
  }

  private void callbackFunction(Void result, Throwable throwable) {
    //some code when complete
  }
}

测试将如下所示:

class RunnerTest {

  @Test
  void test() {
    new Runner("param", (response, throwable) -> /* mocked behavior */).run();
  }
}

-1

我的第一反应是不要嘲笑这个:看起来startAsync是MyClassImplementRunner的公共API的一部分,你应该一起测试这些部分。在像MyClassImplementRunnerTest这样的测试类中,将被测试的系统视为MyClassImplementRunner而不尝试将其拆分是有意义的。否则,很容易失去你正在测试什么,包括什么是真实的什么是模拟的

如果MyClassImplementRunner正在寻找任何外部条件,您可以模拟该依赖项,这可能会导致您的CompletableFuture立即返回;但是,您只显示了一个字符串参数。

话虽如此,startAsync可能包含您想要彻底测试而没有真正的MyClassImplementRunner的逻辑。在这种情况下,您可以创建一个重载以进行测试,可能具有有限的可见性或test-only annotations以指示它不应在生产中调用。

public static CompletableFuture<Void> startAsync(String param1) {
  return startAsync(new MyClassImplementRunner(param1);
}

/** Package-private, for a test class in the same package. */
@VisibleForTesting static CompletableFuture<Void> startAsync(Runnable task) {

  return CompletableFuture.runAsync(task).whenComplete(
      (response, throwable) -> {
        //some code when complete
    });
}

通过拆分,您现在可以在测试中运行startAsync(new Runnable())以模拟立即成功的任务,并运行startAsync(() -> { throw new RuntimeException(); })以模拟立即失败的任务。这使您能够独立测试startAsync而不受MyClassImplementRunner的影响。
对于为测试重构或引入仅供测试使用的方法可能看起来不明智,这是一个公正的评估:纯粹地说,MyClassImplementRunner应该像消费者一样运行它进行测试,而不是模拟。但是,如果您认为在测试中使用与MyClassImplementRunner不同的Runnable更加方便,那么您可以通过在您控制的代码中包含适当的灵活性(“测试接缝”)来为此做好准备。实际上,如果startAsync是一个足够独立的方法,可以接受任意的Runnable,您可以选择将其分离为具有单独测试的单独方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接