Как смоделировать завершение CompletableFuture в Mockito - PullRequest
0 голосов
/ 27 октября 2018

Я хочу смоделировать, что некоторый код вызывается после успешного завершения 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.

Не могли бы вы дать некоторые указания о том, как этого добиться?

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

Извлеките код, который вы выполняете в 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();
  }
}
0 голосов
/ 29 октября 2018

Мое первое желание - , чтобы не издеваться над этим : похоже, startAsync является частью открытого API MyClassImplementRunner, и что вы должны тестировать эти части вместе.В тестовом классе, таком как MyClassImplementRunnerTest, имеет смысл рассматривать тестируемую систему как MyClassImplementRunner, не пытаясь разделить ее.В противном случае очень легко потерять след того, что вы тестируете , включая что реально против что такое макет .

Еслисуществует любое внешнее условие, которое ищет MyClassImplementRunner, вы можете смоделировать эту зависимость, которая, вероятно, приведет к немедленному возвращению вашего CompletableFuture;тем не менее, вы показали нам только один параметр String.

Тем не менее, вполне возможно, что startAsync содержит логику, которую вы хотите полностью протестировать без реального MyClassImplementRunner.В этом случае вы можете создать перегрузку для тестирования, возможно, с ограниченной видимостью или аннотации только для теста , чтобы указать, что он не должен вызываться в рабочей среде.

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.

Может показаться нецелесообразным проводить рефакторинг для тестирования или вводить методы, предназначенные только для тестирования, и это справедливая оценка. будет проверено именно так, как его запустят потребители, без насмешек.Однако, если вы говорите, что в тестах гораздо удобнее запускать с другим Runnable, чем MyClassImplementRunner, вы контролируете код и можете подготовиться к этому, включив в код соответствующую гибкость («тестовый шов»)вы контролируетеНа самом деле, если startAsync является достаточно отдельным методом, который может принимать произвольный Runnable, вы можете выделить его как отдельный метод с отдельным тестированием.

...