Как имитировать завершение CompletableFuture в Mockito

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

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
0
11 635
2

Ответы 2

Моя первая склонность - не издеваться над этим: похоже, что 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.

Может показаться неразумным проводить рефакторинг для тестирования или вводить методы, предназначенные только для тестирования, и это справедливая оценка: чисто говоря, MyClassImplementRunner должен должен быть протестирован точно так, как его запустили бы потребители, без насмешек. Однако, если вы говорите, что в тестах гораздо удобнее запускать с другим Runnable, чем MyClassImplementRunner, вы контролируете код и можете подготовиться к этому, включив в код соответствующую гибкость («тестовый шов»). вы контролируете. Фактически, если 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();
  }
}

Другие вопросы по теме