Я хочу издеваться над тем, что какой-то код вызывается, когда 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 завершается успешно.
Не могли бы вы указать, как этого добиться?
Моя первая склонность - не издеваться над этим: похоже, что 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();
}
}