简体   繁体   中英

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.

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

//some code when complete 

is called when Future is completed successfully.

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.

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. In a test class like MyClassImplementRunnerTest, it makes sense to treat the system under test as MyClassImplementRunner without trying to split it up. 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; 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. 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. This allows you to test startAsync independently from MyClassImplementRunner.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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