如何在 Mockito 中模拟 CompletableFuture 的完成

我想模拟当 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 成功完成时调用。

您能否就如何实现这一目标提供一些指示?

stack overflow How to mock completion of a CompletableFuture in Mockito
原文答案

答案:

作者头像

我的第一个倾向是 not to mock this :看起来 startAsync 是 MyClassImplementRunner 的公共 API 的一部分,您应该一起测试这些部分。在像 MyClassImplementRunnerTest 这样的测试类中,将被测系统视为 MyClassImplementRunner 而不会尝试将其拆分是有意义的。否则,很容易忘记 what you're testing ,包括 what is realwhat is a mock

如果 MyClassImplementRunner 正在寻找任何 external 条件,您可以模拟该依赖项,这可能会导致您的 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(); }) 来模拟立即失败的任务。这允许您独立于 MyClassImplementRunner 测试 startAsync

为测试重构或引入仅测试方法似乎并不明智,这是一个公平的评估:纯粹地说,MyClassImplementRunner should 完全按照消费者运行它的方式进行测试,无需模拟。但是,如果您说在测试中使用与 MyClassImplementRunner 不同的 Runnable 运行会更方便,那么您可以控制代码,并且可以通过在代码中包含适当的灵活性(“测试接缝”)来为此做好准备你控制。事实上,如果 startAsync 是一个足够独立的方法,它可以采用任意 Runnable,那么您可以选择将其分离为单独测试的单独方法。

作者头像

将您在 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();
  }
}