JUnit을 사용하여 비동기 프로세스를 테스트하는 방법


204

JUnit으로 비동기 프로세스를 시작하는 메소드를 어떻게 테스트합니까?

테스트가 프로세스가 끝날 때까지 기다리는 방법을 모르겠습니다 (정확히 단위 테스트가 아니라 하나의 클래스가 아닌 여러 클래스가 포함되므로 통합 테스트와 비슷합니다).


JAT (Java Asynchronous Test)를 사용해보십시오 : bitbucket.org/csolar/jat
cs0lar

2
JAT에는 1 명의 감시자가 있으며 1.5 년 동안 업데이트되지 않았습니다. Awaitility는 1 개월 전에 업데이트되었으며이 문서를 작성할 당시 버전 1.6에 있습니다. 나는 어느 프로젝트와도 관련이 없지만 프로젝트에 추가로 투자하려고한다면 현재 Awaitility에 더 많은 신뢰를 줄 것입니다.
레 Hazlewood

JAT에는 아직 업데이트가 없습니다 : "최종 업데이트 2013-01-19". 링크를 따라 가려면 시간을 절약하십시오.
deamon

@LesHazlewood, 하나의 감시자는 JAT에 좋지 않지만 몇 년 동안 업데이트되지는 않습니다 ... 단지 하나의 예일뿐입니다. 작동하는 경우 OS의 저수준 TCP 스택을 얼마나 자주 업데이트합니까? JAT의 대안은 stackoverflow.com/questions/631598/… 아래에 답변되어 있습니다 .
user1742529

답변:


46

IMHO 스레드 등을 작성하거나 대기하는 단위 테스트를 수행하는 것은 좋지 않습니다. 이러한 테스트를 몇 초 내에 실행하려고합니다. 그렇기 때문에 비동기 프로세스 테스트에 2 단계 접근 방식을 제안하고자합니다.

  1. 비동기 프로세스가 올바르게 제출되었는지 테스트하십시오. 비동기 요청을 수락하는 객체를 조롱하고 제출 된 작업에 올바른 속성 등이 있는지 확인할 수 있습니다.
  2. 비동기 콜백이 올바르게 작동하는지 테스트하십시오. 여기에서 원래 제출 된 작업을 조롱하고 제대로 초기화되었다고 가정하고 콜백이 올바른지 확인할 수 있습니다.

148
확실한. 그러나 때로는 스레드를 관리 해야하는 코드를 테스트해야 할 때가 있습니다.
부족 23:06

77
Junit 또는 TestNG를 사용하여 통합 테스트 (및 단위 테스트가 아닌) 또는 사용자 승인 테스트 (예 : Cucumber)를 수행하는 사용자에게는 비동기 완료를 기다리고 결과를 확인하는 것이 절대적으로 필요합니다.
레 Hazlewood

38
비동기 프로세스는 가장 복잡한 코드 중 일부이며 단위 테스트를 사용하지 말고 단일 스레드로만 테스트해야한다고 말하는가? 아주 나쁜 생각입니다.
찰스

18
모의 테스트는 종종 기능이 끝없이 작동한다는 것을 증명하지 못합니다. 비동기 기능은 작동하도록 비동기 방식으로 테스트해야합니다. 원하는 경우 통합 테스트라고 부르지 만 여전히 필요한 테스트입니다.
Scott Boring

4
이것은 정답이 아니어야합니다. 테스트는 단위 테스트 그 이상입니다. OP는 단위 테스트보다 더 많은 통합 테스트라고 부릅니다.
예레미야 아담스

190

대안은 CountDownLatch 클래스 를 사용하는 것 입니다.

public class DatabaseTest {

    /**
     * Data limit
     */
    private static final int DATA_LIMIT = 5;

    /**
     * Countdown latch
     */
    private CountDownLatch lock = new CountDownLatch(1);

    /**
     * Received data
     */
    private List<Data> receiveddata;

    @Test
    public void testDataRetrieval() throws Exception {
        Database db = new MockDatabaseImpl();
        db.getData(DATA_LIMIT, new DataCallback() {
            @Override
            public void onSuccess(List<Data> data) {
                receiveddata = data;
                lock.countDown();
            }
        });

        lock.await(2000, TimeUnit.MILLISECONDS);

        assertNotNull(receiveddata);
        assertEquals(DATA_LIMIT, receiveddata.size());
    }
}

참고 잠금 대기 메소드가 호출되기 전에 빠른 콜백이 잠금을 해제 할 수 있으므로 일반 객체와 동기화 된 상태로만 사용할 수는 없습니다 . Joe Walnes 의이 블로그 게시물을 참조하십시오 .

EDIT @jtahlborn 및 @Ring의 의견 덕분에 CountDownLatch 주변의 동기화 된 블록이 제거되었습니다.


8
이 예를 따르지 마십시오. 올바르지 않습니다. 당신은해야 하지 내부적으로 스레드 안전을 처리 같은 해, CountDownLatch에 동기화 될 수있다.
jtahlborn 2016 년

1
동기화 된 부분까지는 최대 3-4 시간의 디버깅 시간이 소요되는 것이 좋습니다. stackoverflow.com/questions/11007551/…
Ring

2
오류에 대한 사과드립니다. 답변을 적절하게 편집했습니다.
Martin

7
onSuccess가 호출되었는지 확인하는 경우 lock.await가 true를 리턴한다고 주장해야합니다.
길버트

1
@Martin은 맞지만 수정해야 할 다른 문제가 있음을 의미합니다.
George Aristy

75

Awaitility 라이브러리를 사용해 볼 수 있습니다 . 말하는 시스템을 쉽게 테스트 할 수 있습니다.


21
친절한 면책 조항 : Johan은이 프로젝트의 주요 공헌자입니다.
dbm

1
기다려야 하는 근본적인 문제가 있습니다 (단위 테스트는 빠르게 실행되어야 함 ). 이상적으로는 실제로 필요한 것보다 밀리 초 이상 기다리기를 원하지 않기 때문에 CountDownLatch(@Martin의 답변 참조)이 더 좋습니다.
George Aristy

정말 대단합니다.
R. Karlus

이것은 비동기 프로세스 통합 테스트 요구 사항을 충족시키는 완벽한 라이브러리입니다. 정말 대단합니다. 라이브러리는 잘 관리 된 것으로 보이며 기본에서 고급으로 확장되어 대부분의 시나리오에 적합하다고 생각합니다. 멋진 참조 주셔서 감사합니다!
Tanvir

정말 멋진 제안입니다. 감사합니다
RoyalTiger

68

당신이 사용하는 경우 CompletableFuture (자바 8에 도입) 또는 SettableFuture을 에서 ( 구글 구아바를 )가 아니라 시간의 미리 설정된 금액을 기다리는 것보다, 끝났어, 당신은 곧대로 시험 마무리를 할 수 있습니다. 테스트는 다음과 같습니다.

CompletableFuture<String> future = new CompletableFuture<>();
executorService.submit(new Runnable() {         
    @Override
    public void run() {
        future.complete("Hello World!");                
    }
});
assertEquals("Hello World!", future.get());

4
... 그리고 당신이 8보다 적은 자바를 시도하는 경우 구아바 SettableFuture 를 시도해보십시오
Markus T


18

비동기 메소드를 테스트하는 데 매우 유용한 방법 중 하나는 Executor테스트 대상의 생성자에 인스턴스를 주입하는 것 입니다. 프로덕션에서 executor 인스턴스는 비동기 적으로 실행되도록 구성되며 테스트 중에는 동 기적으로 실행되도록 조롱 할 수 있습니다.

비동기 메소드를 테스트하려고한다고 가정 해보십시오 Foo#doAsync(Callback c).

class Foo {
  private final Executor executor;
  public Foo(Executor executor) {
    this.executor = executor;
  }

  public void doAsync(Callback c) {
    executor.execute(new Runnable() {
      @Override public void run() {
        // Do stuff here
        c.onComplete(data);
      }
    });
  }
}

생산, 나는 구성 할 FooExecutors.newSingleThreadExecutor()시험에 아마 다음을 수행하는 동기 집행자로를 구성 할 때 집행자 인스턴스 -

class SynchronousExecutor implements Executor {
  @Override public void execute(Runnable r) {
    r.run();
  }
}

이제 비동기 메소드에 대한 JUnit 테스트는 매우 깨끗합니다.

@Test public void testDoAsync() {
  Executor executor = new SynchronousExecutor();
  Foo objectToTest = new Foo(executor);

  Callback callback = mock(Callback.class);
  objectToTest.doAsync(callback);

  // Verify that Callback#onComplete was called using Mockito.
  verify(callback).onComplete(any(Data.class));

  // Assert that we got back the data that we expected.
  assertEquals(expectedData, callback.getData());
}

Spring과 같은 비동기 라이브러리 호출과 관련된 것을 통합 테스트하려는 경우 작동하지 않습니다.WebClient
Stefan Haberl

8

스레드 / 비동기 코드 테스트에는 본질적으로 아무런 문제가 없습니다. 특히 스레딩 이 테스트중인 코드 의 포인트 인 경우에는 더욱 그렇습니다 . 이 내용을 테스트하는 일반적인 방법은 다음과 같습니다.

  • 메인 테스트 스레드 차단
  • 다른 스레드에서 실패한 어설 션 캡처
  • 메인 테스트 스레드 차단 해제
  • 실패를 다시 던져라

그러나 그것은 한 번의 테스트를위한 많은 상용구입니다. 더 좋고 간단한 접근법은 ConcurrentUnit을 사용하는 것입니다 .

  final Waiter waiter = new Waiter();

  new Thread(() -> {
    doSomeWork();
    waiter.assertTrue(true);
    waiter.resume();
  }).start();

  // Wait for resume() to be called
  waiter.await(1000);

CountdownLatch접근 방식에 비해이 방법 의 장점은 모든 스레드에서 발생하는 어설 션 오류가 기본 스레드에 올바르게보고되므로 테스트가 실패 할 경우 더 자세하지 않다는 것입니다. CountdownLatchConcurrentUnit에 대한 접근 방식을 비교 한 글 이 있습니다. .

또한 좀 더 자세히 배우고 싶은 사람들을 위해 주제에 대한 블로그 게시물 을 작성했습니다 .


과거에 사용한 비슷한 솔루션은 github.com/MichaelTamm/junit-toolbox 이며 junit.org/junit4의 타사 확장 기능으로도 사용 됩니다
dschulten

4

어떻게 호출에 대해 SomeObject.waitnotifyAll설명으로 여기 또는 사용 Robotiums의 Solo.waitForCondition(...) 방법 또는 용도 클래스를 내가 쓴 이 작업을 수행 (사용하는 방법에 대한 의견과 테스트 클래스를 참조)


1
wait / notify / interrupt 접근 방식의 문제점은 테스트중인 코드가 잠재적으로 대기중인 스레드를 방해 할 수 있다는 것입니다. 그렇기 때문에 ConcurrentUnit 은 스레드가 대기 할 수 있는 전용 회로 를 사용하므로 기본 테스트 스레드의 인터럽트로 인해 실수로 방해받을 수 없습니다.
Jonathan

3

비동기 논리를 테스트 하는 라이브러리 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
멋진 접근법!
aminography

3

그것은 매우 유용 장이 있음을 언급 할만큼 가치의 Testing Concurrent Programs에서 연습에 동시성 테스트 접근 몇 가지 장치를 설명하고 문제에 대한 솔루션을 제공합니다.


1
어떤 접근법입니까? 예를 들어 주시겠습니까?
브루노 페레이라

2

테스트 결과가 비동기 적으로 생성되면 요즘 사용하고 있습니다.

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일찍 실패하는 데 사용할 수도 있습니다 .


2

여기에는 많은 대답이 있지만 간단한 대답은 완성 된 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();

모든 CompletableFuture가 완료되면 바로 압축됩니다!


2

가능하면 병렬 스레드로 테스트하지 마십시오 (대부분의 경우). 이렇게하면 테스트가 벗겨지기 만합니다 (때로는 통과하고 때로는 실패합니다).

다른 라이브러리 / 시스템을 호출해야하는 경우에만 다른 스레드를 기다려야 할 수도 있습니다.이 경우 항상 Awaitility 라이브러리 대신을 사용하십시오 Thread.sleep().

전화를 걸 get()거나 join()테스트 하지 마십시오 . 그렇지 않으면 미래가 완료되지 않을 경우 테스트가 CI 서버에서 영원히 실행될 수 있습니다. isDone()전화하기 전에 항상 테스트에서 먼저 주장 하십시오 get(). CompletionStage의 경우는입니다 .toCompletableFuture().isDone().

다음과 같이 비 차단 방법을 테스트 할 때 :

public static CompletionStage<String> createGreeting(CompletableFuture<String> future) {
    return future.thenApply(result -> "Hello " + result);
}

그런 다음 테스트에서 완성 된 Future를 전달하여 결과를 테스트 할뿐만 아니라 또는 doSomething()을 호출 하여 메서드 가 차단되지 않아야 합니다. 비 차단 프레임 워크를 사용하는 경우 특히 중요합니다.join()get()

이를 위해서는 수동으로 완료하도록 설정 한 완료되지 않은 미래로 테스트하십시오.

@Test
public void testDoSomething() throws Exception {
    CompletableFuture<String> innerFuture = new CompletableFuture<>();
    CompletableFuture<String> futureResult = createGreeting(innerFuture).toCompletableFuture();
    assertFalse(futureResult.isDone());

    // this triggers the future to complete
    innerFuture.complete("world");
    assertTrue(futureResult.isDone());

    // futher asserts about fooResult here
    assertEquals(futureResult.get(), "Hello world");
}

그렇게하면 future.join()doSomething ()에 추가 하면 테스트가 실패합니다.

서비스가에서와 같이 ExecutorService를 사용하는 경우 thenApplyAsync(..., executorService)테스트에서 구아바의 단일 스레드 ExecutorService를 주입하십시오.

ExecutorService executorService = Executors.newSingleThreadExecutor();

코드에서와 같은 forkJoinPool thenApplyAsync(...)을 사용하는 경우 코드를 다시 작성하여 ExecutorService (여러 가지 이유가 있음)를 사용하거나 Awaitility를 사용하십시오.

예제를 단축하기 위해 테스트에서 BarService를 Java8 람다로 구현 된 메소드 인수로 만들었습니다. 일반적으로 모의하는 참조가됩니다.


이봐 @tkruse, 아마도이 기술을 사용하여 테스트와 함께 공개 자식 리포지토리가 있습니까?
Cristiano

@ Christiano : 그것은 SO 철학에 위배됩니다. 대신 빈 junit 테스트 클래스에 붙여 넣을 때 추가 코드 (모든 가져 오기는 java8 + 또는 junit 임)없이 컴파일되도록 메소드를 변경했습니다. 공감하십시오.
tkruse

나는 지금 이해했다. 감사. 내 문제는 이제 메서드가 CompletableFuture를 반환하지만 CompletableFuture 이외의 매개 변수로 다른 객체를 허용하는 시점을 테스트하는 것입니다.
Cristiano

귀하의 경우 누가 메소드가 반환하는 CompletableFuture를 작성합니까? 다른 서비스라면 조롱 할 수 있으며 내 기술은 여전히 ​​적용됩니다. 메소드 자체가 CompletableFuture를 작성하는 경우 상황이 매우 많이 바뀌므로 새로운 질문을 할 수 있습니다. 그런 다음 메소드가 리턴하는 미래를 완료 할 스레드에 따라 다릅니다.
tkruse

1

나는 기다림과 통보를 선호한다. 간단하고 명확합니다.

@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];
    }
}

기본적으로 익명의 내부 클래스 내에서 사용하려면 최종 배열 참조를 만들어야합니다. wait ()해야 할 경우 제어 할 값을 넣을 수 있기 때문에 오히려 boolean []을 작성하려고합니다. 모든 것이 끝나면 asyncExecuted를 해제합니다.


1
어설 션이 실패하면 기본 테스트 스레드에서 알 수 없습니다.
Jonathan

솔루션에 감사드립니다. 웹 소켓 연결로 코드를 디버깅하는 데 도움이됩니다.
Nazar Sakharenko

@Jonathan, 나는 어설 션과 예외를 잡아서 메인 테스트 스레드에 알리기 위해 코드를 업데이트했습니다.
Paulo

1

모든 Spring 사용자의 경우 비동기 동작이 관련된 현재 통합 테스트를 수행하는 방법은 다음과 같습니다.

비동기 작업 (예 : I / O 호출)이 완료되면 프로덕션 코드에서 응용 프로그램 이벤트를 시작합니다. 어쨌든이 이벤트는 프로덕션에서 비동기 작업의 응답을 처리하는 데 필요합니다.

이 이벤트가 완료되면 테스트 케이스에서 다음 전략을 사용할 수 있습니다.

  1. 테스트중인 시스템 실행
  2. 이벤트를 듣고 이벤트가 시작되었는지 확인하십시오
  3. 당신의 주장을하세요

이 문제를 해결하려면 먼저 일종의 도메인 이벤트가 필요합니다. 완료된 작업을 식별하기 위해 여기에 UUID를 사용하고 있지만 고유 한 경우 다른 것을 자유롭게 사용할 수 있습니다.

(다음 코드 스 니펫은 롬복 주석을 사용 하여 보일러 플레이트 코드를 제거합니다.)

@RequiredArgsConstructor
class TaskCompletedEvent() {
  private final UUID taskId;
  // add more fields containing the result of the task if required
}

생산 코드 자체는 일반적으로 다음과 같습니다.

@Component
@RequiredArgsConstructor
class Production {

  private final ApplicationEventPublisher eventPublisher;

  void doSomeTask(UUID taskId) {
    // do something like calling a REST endpoint asynchronously
    eventPublisher.publishEvent(new TaskCompletedEvent(taskId));
  }

}

그런 다음 Spring @EventListener을 사용하여 테스트 코드에서 게시 된 이벤트를 잡을 수 있습니다 . 이벤트 리스너는 스레드 안전 방식으로 두 가지 경우를 처리해야하기 때문에 조금 더 복잡합니다.

  1. 프로덕션 코드가 테스트 케이스보다 빠르며 테스트 케이스가 이벤트를 확인하기 전에 이벤트가 이미 시작되었거나
  2. 테스트 케이스는 프로덕션 코드보다 빠르며 테스트 케이스는 이벤트를 기다려야합니다.

CountDownLatch다른 답변에서 언급 한 것처럼 두 번째 경우에는 가 사용됩니다. 또한 @Order이벤트 핸들러 메소드 의 주석은이 이벤트 핸들러 메소드가 프로덕션에 사용 된 다른 이벤트 리스너 후에 호출되도록합니다.

@Component
class TaskCompletionEventListener {

  private Map<UUID, CountDownLatch> waitLatches = new ConcurrentHashMap<>();
  private List<UUID> eventsReceived = new ArrayList<>();

  void waitForCompletion(UUID taskId) {
    synchronized (this) {
      if (eventAlreadyReceived(taskId)) {
        return;
      }
      checkNobodyIsWaiting(taskId);
      createLatch(taskId);
    }
    waitForEvent(taskId);
  }

  private void checkNobodyIsWaiting(UUID taskId) {
    if (waitLatches.containsKey(taskId)) {
      throw new IllegalArgumentException("Only one waiting test per task ID supported, but another test is already waiting for " + taskId + " to complete.");
    }
  }

  private boolean eventAlreadyReceived(UUID taskId) {
    return eventsReceived.remove(taskId);
  }

  private void createLatch(UUID taskId) {
    waitLatches.put(taskId, new CountDownLatch(1));
  }

  @SneakyThrows
  private void waitForEvent(UUID taskId) {
    var latch = waitLatches.get(taskId);
    latch.await();
  }

  @EventListener
  @Order
  void eventReceived(TaskCompletedEvent event) {
    var taskId = event.getTaskId();
    synchronized (this) {
      if (isSomebodyWaiting(taskId)) {
        notifyWaitingTest(taskId);
      } else {
        eventsReceived.add(taskId);
      }
    }
  }

  private boolean isSomebodyWaiting(UUID taskId) {
    return waitLatches.containsKey(taskId);
  }

  private void notifyWaitingTest(UUID taskId) {
    var latch = waitLatches.remove(taskId);
    latch.countDown();
  }

}

마지막 단계는 테스트중인 시스템을 테스트 사례에서 실행하는 것입니다. JUnit 5와 함께 SpringBoot 테스트를 사용하고 있지만 Spring 컨텍스트를 사용하는 모든 테스트에서 동일하게 작동합니다.

@SpringBootTest
class ProductionIntegrationTest {

  @Autowired
  private Production sut;

  @Autowired
  private TaskCompletionEventListener listener;

  @Test
  void thatTaskCompletesSuccessfully() {
    var taskId = UUID.randomUUID();
    sut.doSomeTask(taskId);
    listener.waitForCompletion(taskId);
    // do some assertions like looking into the DB if value was stored successfully
  }

}

여기의 다른 답변과 달리이 솔루션은 테스트를 병렬로 실행하고 여러 스레드가 동시에 비동기 코드를 행사하는 경우에도 작동합니다.


0

로직을 테스트하려면 비동기식으로 테스트하지 마십시오.

예를 들어 비동기 메소드의 결과에서 작동하는이 코드를 테스트합니다.

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

테스트에서 동기 구현과의 종속성을 모의합니다. 단위 테스트는 완전히 동기식이며 150ms에서 실행됩니다.

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")));
    }
}

비동기 동작을 테스트하지는 않지만 논리가 올바른지 테스트 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.