본문 바로가기
프로그래밍/테스트

[UNIT-TEST] Webflux Reactor 유닛 테스트 하기

by 사바라다 2020. 12. 17.

안녕하세요. 오늘은 여러분들과 함께 알아볼 내용은 Webflux에서 채택하고 있는 Reactive Stream 프로젝트인 Reactor를 유닛 테스트하는 방법입니다. Reactive Stream의 경우 기존의 일반적인 로직과 다르게 Publisher(생산)와 Subscriber(구독)로 나누어져 있으며 각각이 서로를 의식하지 않은채 약한 결합으로 돌아갑니다.

그렇기 때문에 기존 우리가 짜던 구성으로는 테스트가 쉽지 않습니다. 따라서 테스팅을 용이하게 하기위한 구성 방법 및 테스팅 방법에 대해서 오늘 여러분들과 함께 공유하고자 합니다.

이번 포스팅에서 테스팅 프레임워크로 Spock을 사용하도록 하겠습니다. JUnit 5와 Reactor 테스팅 방법의 차이는 없으므로 동일하게 사용하셔도 좋습니다.

의존성

webflux의 Reactor를 유닛테스트하기 위해서는 reactor-test 프로젝트를 추가로 의존성을 가져올 필요가 있습니다. 따라서 아래와 같이 의존성을 추가해줍니다.

testImplementation "io.projectreactor:reactor-test:3.2.3.RELEASE"
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
    <version>3.2.3.RELEASE</version>
</dependency>

reactor-test는 Flux가 절 생성 되었는 지 알아보는 테스트와 생성된 Flux가 의도한 대로 변환되고 filter 되는지에 대한 테스트도 지원합니다.

예제

오늘 테스트하고자하는 Reactor의 예제는 아래와 같습니다. 아래 예제의 로직을 설명드리면 위의 메서드는 전체적으로 Flux를 생성합니다. 그 Flux의 신호는 특정 파일에서 값을 읽어옵니다.(여기서는 List.of 로 임시 처리) 그리고 가져온 값들을 Flux의 신호로 사용합니다. 모든 값들의 신호 처리가 끝난 후 종료되었다는 complete 신호를 호출로 마무리 합니다.

public Flux<Long> getGameIdFromFile() {
    return Flux.create(sin -> { // Flux(Publsher) 생성
      List<Long> list = List.of(1L, 5L, 9L, 15L, 20L);
      list.foreach(e -> sin.next(e)); // list를 읽어 각각 값을 신호로 처리
      sin.complete(); // 마무리 되었다는 신호를 처리
    });
}

생성된 신호를 처리하는 과정 중 일부를 가져왔습니다. 아래 코드는 Id가 5의 배수라면 사용하지 않도록 필터처리를 하는 코드입니다. reactor-test를 이용하면 아래코드 역시 getGameIdFromFile과는 별개로 유닛 테스트를 만들 수 있습니다.

public Flux<Long> gameIdFilter(Flux<Long> gameIds) {
  service.getGameIdFromFile()
  .filter(id -> id % 5 != 0)
}

위 코드를 실행하면 아래처럼 console에 출력되게 됩니다.

sub : 1
sub : 5
sub : 9
sub : 15
sub : 20

Publisher 테스트

그렇다면 이제 위 예제 코드를 유닛 테스트를 한번 진행해보도록 하겠습니다. 먼저 Flux 신호를 생성하는 메서드를 테스트 해보겠습니다. 테스트에는 reactor-test 의존성에서 제공해주는 StepVerifier를 이용해야합니다. 사용하였던 예제에 List<Long> list = List.of(1L, 5L, 9L, 15L, 20L); 이런 코드가 있었습니다. 이 Flux의 신호 순서로 1, 5, 9.. 이렇게 나오는걸 예샹할 수 있습니다. 이를 토대로 테스트 코드를 만들어 보았습니다.


def "flux 신호생성 테스트"() {
    when:
    StepVerifier.create(service.getGameIdFromFile()) // 테스트 할 Flux 설정
            .expectNext(1L) // 신호의 순서대로 expectNext 설정
            .expectNext(5L)
            .expectNext(9L, 15L, 20L)
            .expectComplete() // complete 신호 설정
            .verify() // subscribe 대신, 신호 검수 시작
    then:
    true
}

신호를 생성하다 exception 이 발생할 가능성도 있습니다. 그럴 경우를 테스트하기 위해서는 expectError를 이용하면 됩니다. 코드가 아래와 같이 변경되었다면 next 신호를 모두 발생시키고 complete 신호가 발생되지 못한 채 exception이 발생하게 됩니다.

public Flux<Long> getGameIdFromFile() {
    return Flux.create(sin -> { // Flux(Publsher) 생성
      List<Long> list = List.of(1L, 5L, 9L, 15L, 20L);
      list.foreach(e -> sin.next(e)); // list를 읽어 각각 값을 신호로 처리
      throw new RuntimeException();
      sin.complete(); // 마무리 되었다는 신호를 처리
    });
}

테스트 코드에서도 expectComplete가 아닌 expectError를 이용하여 테스트 할 수 있습니다. 좀 더 자세하게 에러 종류까지 확인하고 싶으시다면 expectErrorMatches를 이용하면 됩니다.


def "flux 신호생성 후 에러 테스트"() {
    when:
    StepVerifier.create(service.getGameIdFromFile()) // 테스트 할 Flux 설정
            .expectNext(1L) // 신호의 순서대로 expectNext 설정
            .expectNext(5L)
            .expectNext(9L, 15L, 20L)
            .expectError() // Exception 발생
            .verify() // subscribe 대신, 신호 검수 시작
    then:
    true
}

TestPublisher를 이용한 테스트

TestPublisher는 Flux.createrange 등 처음 생성된 신호가 아니라 중간의 filter 또는 map을 통한 변환과정이 의도된 대로 이루어지는지에 대한 테스트를 진행할 때 사용할 수 있습니다. 위의 예제로 보자면 정상적으로 Id가 5인 숫자는 filter 된다는 것을 증명하면 됩니다.

def "filter 테스트"() {
   given:
    TestPublisher<Long> testPublisher = TestPublisher.<Long> create();

    when:
    StepVerifier.create(gameIdFilter(testPublisher.flux())) // testPublisher를 flux로 전환
            .then({ -> testPublisher.emit(1L, 3L, 5L, 7L, 10L) }) // 원하는 값 emit
            .expectNext(1L, 3L, 7L) // filter 메서드를 통해 filter 된 emit 값 정상 확인
            .verifyComplete()

    then:
    true
}

테스트가 정상적으로 마무리 되었음을 확인하였습니다.

마무리

오늘은 이렇게 Reactor를 테스트하는 방법에 대해서 알아보는 시간을 가져보았습니다.

사실 Reactor 사용법도 아직 포스팅하지 않았는데 테스팅 방법을 먼저 포스팅한다는게 좀 웃기긴 합니다.

추후에 Reactor를 사용하는 방법에 대해서 정리해서 올려보도록 하겠습니다.

감사합니다.

참조

baeldung_reactive-streams-step-verifier-test-publisher

댓글