0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Webfluxでの単体テスト(Junit)

Posted at

概要

  • SpringWebで書いていたテストのあれこれをwebfluxではどう書くのか気になったので調べてみました

WebClient利用クラス

WebClient利用している外部APIを呼び出すクラスについてです。

テスト対象のクラス

@Component
public class JunitSampleClient {
    private final WebClient webClient;

    public JunitSampleClient(@Value("{baseUrl}") String baseUrl) {
        this.webClient = WebClient.builder()
                .baseUrl(baseUrl)
                .build();
    }

    public Mono<String> ping() {
        return webClient.get()
              .uri("/ping")
              .retrieve()
              .bodyToMono(String.class);
    }
}

このクラスをテストしようとした時に考えることは以下の2つかなと思います。

  • WebClientをどうMock化するか?
  • Mono(戻り値)をどう検証するか?

それぞれ見ていきます

WebClientをどうMock化するか?

WebClientのMockをしようとすると、メソッドチェーン分のMockを準備してあげなければならず、大変。。。
なので「mockwebserver」を使用してモックサーバを立ち上げます

まずは必要なライブラリを追加します

build.gradle
dependencies {
    ...
    testImplementation 'com.squareup.okhttp3:okhttp:4.9.1'
    testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.1'
    ...
}

モックサーバはMockWebServerを起動すればOK!

// 起動
MockWebServer mockServer = new MockWebServer();
// 停止
mockServer.shutdown();

そしてenqueueメソッドでAPIのレスポンスを定義してあげます。

mockServer.enqueue(new MockResponse()
        .setBody("pong"));

モックサーバは、ランダムなポートで起動するためどのポートで起動したか確認するためにgetPort()メソッドが用意されています。

↓参考↓

Mono(戻り値)をどう検証するか?

WebClientに限らず、Mono, Fluxを使う場合に通じて言えることですが、
Monoの検証にはStepVerifierを使います。

StepVerifier.create(Mono.just("pong"))
        // 要素の検証(fluxの場合には、メソッドチェーンで複数回実行)
        .expectNext("pong")
        // 要素がもうないことを検証
        .expectComplete()
        // 検証実行
        .verify();

block()メソッドで、要素を取り出すことで非reactと同じようにテストをすることも可能ですが、
リアクティブならではの動作(例:delay)の検証はできないため
慣れるためにもStepVerifierを使う方が良いのではないかと思います。

注意点としてはverify()を呼び出して初めてexpectXXX()の内容が検証されるので、
すべてのテストで必ずverify()を呼び出すことです。

テストクラス

上記を踏まえて実装したテストがこちら

class JunitSampleClientTest {
    private JunitSampleClient junitSampleClient;

    public static MockWebServer mockServer;
    private static String baseUrl;

    @BeforeAll
    static void setUp() throws IOException {
        mockServer = new MockWebServer();
        mockServer.start();
        baseUrl = "http://localhost:" + mockServer.getPort();
    }

    @AfterAll
    static void tearDown() throws IOException {
        if (mockServer != null) {
            mockServer.shutdown();
        }
    }

    @BeforeEach
    void beforeEach() {
        junitSampleClient = new JunitSampleClient(baseUrl);
    }

    @Test
    void test() {
        mockServer.enqueue(new MockResponse()
                .setBody("pong"));
        Mono<String> response = junitSampleClient.ping();

        StepVerifier.create(response)
                // 要素の検証(fluxの場合には、メソッドチェーンで複数回実行)
                .expectNext("pong")
                // 要素がもうないことを検証
                .expectComplete()
                // 検証実行
                .verify();
    }
}

Controller

テスト対象のクラス

@RestController
@RequiredArgsConstructor
public class JunitSampleController {
    private final JunitSampleClient junitSampleClient;

    @GetMapping("/junit/sample")
    public Mono<String> sample() {
        return junitSampleClient.ping();
    }
}

さきほどのJunitSampleClientを依存にもつControllerです。

spring-webの場合、Controllerのテストは@MockMvcTestを使用してテストを実行します。
spring-webfluxの場合はどうなのでしょう?

それを踏まえ、このクラスをテストしようとした時に考えることは以下の2つかなと思います。

  • webflux版@MockMvcTestは?
  • 依存コンポーネントをどうMock化するか?

それぞれ見ていきます。

webflux版@MockMvcTestは?

結論から言うと@WebFluxTestを使用します。
@WebFluxTestを使用すると、WebTestClientという、

  • ローカルで起動するportに対応したbaseUrlを設定してくれていて
  • レスポンスの検証メソッドが用意されている

クラスがBeanに登録されるので、Autowiredで取得します。

// 引数にテスト対象のクラスを設定する
@WebFluxTest(JunitSampleController.class)
class JunitSampleControllerTest {
    @Autowired
    private WebTestClient webClient;
}

テスト時はWebClientと同じ使い方でリクエストし、
.expectXXXというメソッドで各種検証をしてきます。

以下ではステータスとbodyを検証しています。

webClient.get()
        .uri("/junit/sample")
        .exchange()
        // ↑↑↑ ここまではWebClientと同じ使い方
        // ↓↓↓ ここからは検証
        .expectStatus().isOk()
        .expectBody(String.class).isEqualTo("pong");

依存コンポーネントをどうMock化するか?

@WebFluxTestを使用すると、Controllerに必要なBeanのみにしぼってSpringBootが起動するため、
依存をMock化したければMockBeanで登録されているBeanをMock化してあげます。

@MockBean
private JunitSampleClient junitSampleClient;
...
Mockito.when(junitSampleClient.ping())
        .thenReturn(Mono.just("pong"));

これは知っている人には当然なことかもしれませんが
実際動かしてみるまで「MockBeanでいいんだっけ?」と半信半疑だったので一応

テストクラス

上記を踏まえて実装したテストがこちら

@WebFluxTest(JunitSampleController.class)
class JunitSampleControllerTest {
    @Autowired
    private WebTestClient webClient;

    @MockBean
    private JunitSampleClient junitSampleClient;

    @Test
    void test() {
        Mockito.when(junitSampleClient.ping())
                .thenReturn(Mono.just("pong"));

        webClient.get()
                .uri("/junit/sample")
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("pong");
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?