Как использовать JUnit для тестирования асинхронных процессов - PullRequest
166 голосов
/ 10 марта 2009

Как вы тестируете методы, которые запускают асинхронные процессы с JUnit?

Я не знаю, как заставить мой тест ждать завершения процесса (это не совсем модульный тест, это больше похоже на интеграционный тест, поскольку он включает в себя несколько классов, а не только один).

Ответы [ 15 ]

2 голосов
/ 03 марта 2017

Здесь есть много ответов, но простой - просто создать законченное CompletableFuture и использовать его:

CompletableFuture.completedFuture("donzo")

Итак, в моем тесте:

this.exactly(2).of(mockEventHubClientWrapper).sendASync(with(any(LinkedList.class)));
this.will(returnValue(new CompletableFuture<>().completedFuture("donzo")));

Я просто уверен, что все эти штуки все равно будут называться. Этот метод работает, если вы используете этот код:

CompletableFuture.allOf(calls.toArray(new CompletableFuture[0])).join();

Он будет проходить прямо через него, так как все CompletableFutures завершены!

1 голос
/ 17 апреля 2017

Я нахожу библиотеку socket.io для проверки асинхронной логики. Это выглядит просто и кратко, используя LinkedBlockingQueue . Вот пример :

    @Test(timeout = TIMEOUT)
public void message() throws URISyntaxException, InterruptedException {
    final BlockingQueue<Object> values = new LinkedBlockingQueue<Object>();

    socket = client();
    socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
        @Override
        public void call(Object... objects) {
            socket.send("foo", "bar");
        }
    }).on(Socket.EVENT_MESSAGE, new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            values.offer(args);
        }
    });
    socket.connect();

    assertThat((Object[])values.take(), is(new Object[] {"hello client"}));
    assertThat((Object[])values.take(), is(new Object[] {"foo", "bar"}));
    socket.disconnect();
}

Используя LinkedBlockingQueue, возьмите API для блокировки, пока не получите результат, как синхронный способ И установите таймаут, чтобы не тратить слишком много времени на ожидание результата.

1 голос
/ 29 декабря 2016

Это то, что я использую сейчас, если результат теста генерируется асинхронно.

public class TestUtil {

    public static <R> R await(Consumer<CompletableFuture<R>> completer) {
        return await(20, TimeUnit.SECONDS, completer);
    }

    public static <R> R await(int time, TimeUnit unit, Consumer<CompletableFuture<R>> completer) {
        CompletableFuture<R> f = new CompletableFuture<>();
        completer.accept(f);
        try {
            return f.get(time, unit);
        } catch (InterruptedException | TimeoutException e) {
            throw new RuntimeException("Future timed out", e);
        } catch (ExecutionException e) {
            throw new RuntimeException("Future failed", e.getCause());
        }
    }
}

При использовании статического импорта тест выглядит довольно хорошо. (обратите внимание, в этом примере я запускаю тему, чтобы проиллюстрировать идею)

    @Test
    public void testAsync() {
        String result = await(f -> {
            new Thread(() -> f.complete("My Result")).start();
        });
        assertEquals("My Result", result);
    }

Если f.complete не вызывается, тест завершится неудачно после истечения времени ожидания. Вы также можете использовать f.completeExceptionally для раннего сбоя.

1 голос
/ 01 декабря 2015

Я предпочитаю использовать ожидание и уведомление. Это просто и понятно.

@Test
public void test() throws Throwable {
    final boolean[] asyncExecuted = {false};
    final Throwable[] asyncThrowable= {null};

    // do anything async
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // Put your test here.
                fail(); 
            }
            // lets inform the test thread that there is an error.
            catch (Throwable throwable){
                asyncThrowable[0] = throwable;
            }
            // ensure to release asyncExecuted in case of error.
            finally {
                synchronized (asyncExecuted){
                    asyncExecuted[0] = true;
                    asyncExecuted.notify();
                }
            }
        }
    }).start();

    // Waiting for the test is complete
    synchronized (asyncExecuted){
        while(!asyncExecuted[0]){
            asyncExecuted.wait();
        }
    }

    // get any async error, including exceptions and assertationErrors
    if(asyncThrowable[0] != null){
        throw asyncThrowable[0];
    }
}

По сути, нам нужно создать окончательную ссылку на массив, которая будет использоваться внутри анонимного внутреннего класса. Я бы предпочел создать логическое значение [], потому что я могу поставить значение для управления, если нам нужно ждать (). Когда все будет сделано, мы просто выпускаем asyncExecuted.

0 голосов
/ 29 августа 2016

Если вы хотите проверить логику, просто не проверяйте ее асинхронно.

Например, чтобы протестировать этот код, который работает с результатами асинхронного метода.

public class Example {
    private Dependency dependency;

    public Example(Dependency dependency) {
        this.dependency = dependency;            
    }

    public CompletableFuture<String> someAsyncMethod(){
        return dependency.asyncMethod()
                .handle((r,ex) -> {
                    if(ex != null) {
                        return "got exception";
                    } else {
                        return r.toString();
                    }
                });
    }
}

public class Dependency {
    public CompletableFuture<Integer> asyncMethod() {
        // do some async stuff       
    }
}

В тесте смоделирована зависимость с синхронной реализацией. Модульное тестирование полностью синхронно и выполняется за 150 мс.

public class DependencyTest {
    private Example sut;
    private Dependency dependency;

    public void setup() {
        dependency = Mockito.mock(Dependency.class);;
        sut = new Example(dependency);
    }

    @Test public void success() throws InterruptedException, ExecutionException {
        when(dependency.asyncMethod()).thenReturn(CompletableFuture.completedFuture(5));

        // When
        CompletableFuture<String> result = sut.someAsyncMethod();

        // Then
        assertThat(result.isCompletedExceptionally(), is(equalTo(false)));
        String value = result.get();
        assertThat(value, is(equalTo("5")));
    }

    @Test public void failed() throws InterruptedException, ExecutionException {
        // Given
        CompletableFuture<Integer> c = new CompletableFuture<Integer>();
        c.completeExceptionally(new RuntimeException("failed"));
        when(dependency.asyncMethod()).thenReturn(c);

        // When
        CompletableFuture<String> result = sut.someAsyncMethod();

        // Then
        assertThat(result.isCompletedExceptionally(), is(equalTo(false)));
        String value = result.get();
        assertThat(value, is(equalTo("got exception")));
    }
}

Вы не проверяете асинхронное поведение, но можете проверить правильность логики.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...