简体   繁体   English

如何在 Mockito 中模拟 CompletableFuture 的完成

[英]How to mock completion of a CompletableFuture in Mockito

I want to mock that some code is being called when a CompletableFuture has completed successfully.我想模拟当CompletableFuture成功完成时正在调用一些代码。

I have this class:我有这堂课:

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
        }
    }
}

In my Junit (using Mockito and Java 8), I need to mock that在我的 Junit(使用 Mockito 和 Java 8)中,我需要模拟它

//some code when complete 

is called when Future is completed successfully.当 Future 成功完成时调用。

Could you provide some indications on how to achieve this?您能否就如何实现这一目标提供一些指示?

Extract the code you execute in whenComplete to a field and provide a constructor to replace it.将您在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
  }
}

The test will look as follows:测试将如下所示:

class RunnerTest {

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

My first inclination is not to mock this : It looks like startAsync is a part of MyClassImplementRunner's public API, and that you should be testing these pieces together.我的第一个倾向不是模拟这个:看起来startAsync是 MyClassImplementRunner 的公共 API 的一部分,你应该一起测试这些部分。 In a test class like MyClassImplementRunnerTest, it makes sense to treat the system under test as MyClassImplementRunner without trying to split it up.在像 MyClassImplementRunnerTest 这样的测试类中,将被测系统视为 MyClassImplementRunner 而不会尝试将其拆分是有意义的。 Otherwise, it's very easy to lose track of what you're testing , including what is real versus what is a mock .否则,很容易忘记您正在测试的内容,包括真实内容与模拟内容

If there is any external condition that MyClassImplementRunner is looking for, you can mock that dependency, which would likely cause your CompletableFuture to return immediately;如果 MyClassImplementRunner 正在寻找任何外部条件,您可以模拟该依赖项,这可能会导致您的 CompletableFuture 立即返回; however, you've only shown us a single String parameter.但是,您只向我们展示了一个字符串参数。

That said, it's possible that startAsync contains logic that you'd like to test exhaustively without a real MyClassImplementRunner.也就是说, startAsync可能包含您希望在没有真正的 MyClassImplementRunner 的情况下彻底测试的逻辑。 In that case, you could create an overload for testing, possibly with limited visibility or test-only annotations to indicate that it shouldn't be called in production.在这种情况下,您可以为测试创建重载,可能具有有限的可见性或仅测试注释以指示不应在生产中调用它。

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
    });
}

By splitting this up, you can now run startAsync(new Runnable()) in tests to simulate an instantly-succeeding task, and run startAsync(() -> { throw new RuntimeException(); }) to simulate an instantly-failing task.通过拆分,您现在可以在测试中运行startAsync(new Runnable())以模拟即时成功的任务,并运行startAsync(() -> { throw new RuntimeException(); })以模拟即时失败的任务. This allows you to test startAsync independently from MyClassImplementRunner.这允许您独立于 MyClassImplementRunner 测试startAsync

It may not seem wise to refactor for testing or to introduce test-only methods, and this is a fair assessment: Purely speaking, MyClassImplementRunner should be tested exactly as consumers would run it, without mocking.为测试重构或引入仅测试方法似乎并不明智,这是一个公平的评估:纯粹地说,MyClassImplementRunner应该完全按照消费者运行它的方式进行测试,而不是模拟。 However, if you're saying it is much more convenient in tests to run with a different Runnable than MyClassImplementRunner, you are in control of the code and you can prepare for this by including the appropriate flexiblity ("testing seam") in the code you control.但是,如果您说在测试中使用与 MyClassImplementRunner 不同的 Runnable 运行会更方便,那么您可以控制代码,并且可以通过在代码中包含适当的灵活性(“测试接缝”)来为此做好准备你控制。 In fact, if startAsync is a separate-enough method that it can take an arbitrary Runnable, you may choose to separate it out to be a separate method with separate testing.事实上,如果startAsync是一个足够独立的方法,它可以采用任意 Runnable,那么您可以选择将其分离为单独测试的单独方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM