05. SpringBootのソースをスタブ化してみた

# 概要
SpringBootシリーズ第五弾です。
今回は今までと少し内容を変えて、**ソースをスタブ化する方法**について説明していきます。

よくあるのは**UTにおけるMockitoだったり、FTにおけるwiremockによるモック化**だと思うのですが、
bootRunしてしまうと各モックは動かないため、HTTP通信に失敗してしまったり、DBに繋げなかったりして困るということがあると思います。

今から紹介する方法でスタブ化させると、
**ある環境ではスタブを使用し、対向APIが存在する環境だけは実際のAPIを叩きにいく**ということが可能になります。

困っている方の参考になればと思います！！

# 本題
ソースをスタブ化させるにあたってどんな方法をとるかというと、
**スタブ化させたいクラスと同名のスタブクラスをDIコンテナに登録し、@Primaryを付与してすり替える**、
ということをやります。

## 1. 適当にRestTemplateを使って実装してみる
私の記事では何度か登場してきますが、**郵便番号検索API**を利用します。
その名の通り、郵便番号をクエリパラメタに乗せてリクエストすると、レスポンスボディで住所情報を返却してくれるAPIです。

実装については[こちら](https://qiita.com/supreme0110/items/4b7ab92da519c46b8082)をご覧いただくとして、
どんな結果が返ってくるかbootRunして叩いてみましょう。

まずは存在する住所をリクエストしてみます。
<img width="587" alt="無題1.png" src="https://qiita-image-store.s3.amazonaws.com/0/188440/ac8caced-7acd-46c5-587a-e18abdb03134.png">
無事、郵便番号や都道府県コードなどが取得できていますね。
正常系はこんな感じです。

では存在しない住所をリクエストするとどうなるでしょうか？
<img width="593" alt="無題2.png" src="https://qiita-image-store.s3.amazonaws.com/0/188440/d8b7106e-e48f-0d56-ead2-717e140dd895.png">

当然ですが、**存在しない郵便番号を打てば空のレスポンス**が返ってきます。

…それでは、もしも**郵便番号検索APIが存在しない環境にこのアプリをリリースしなければならなくなったらどうしますか？**
考えるまでもなく**どんな郵便番号をリクエストしたとしても404エラー**となり、使い物にならなくなってしまいます。

そんな時のために**スタブを作って対応**しましょう！！

## 2. スタブクラスを実装する
実装クラスをご覧いただきながら説明したほうがイメージしやすいと思いますので、
まずは実装クラスを載せます。

``` MockClient.java
@Configuration // (1)
public class MockClient {
    @Bean // (1)
    @Primary // (1)
    public GetAddressApiClient mockGetAddressApiClient(RestTemplateBuilder restTemplateBuilder,
                                                       ResponseHandlerInterceptor interceptor) {
        return new MockGetAddressApiClient(restTemplateBuilder, interceptor);
    }

    static class MockGetAddressApiClient extends GetAddressApiClient { // (2)
        MockGetAddressApiClient(RestTemplateBuilder restTemplateBuilder,
                                ResponseHandlerInterceptor interceptor) {
            super(restTemplateBuilder, interceptor);
        }

        @Override
        public GetAddressApiResponse request(String zipCode) {
            return new GetAddressApiResponse( // (3)
                    "200",
                    "",
                    Arrays.asList(
                            new GetAddressApiResponse.Result(
                                    "zipcode",
                                    "prefcode",
                                    "address1",
                                    "address2",
                                    "address3",
                                    "kana1",
                                    "kana2",
                                    "kana3"
                            ),
                            new GetAddressApiResponse.Result(
                                    "zipcode",
                                    "prefcode",
                                    "address1",
                                    "address2",
                                    "address3",
                                    "kana1",
                                    "kana2",
                                    "kana3"
                            )
                    )
            );
        }
    }
}
```

それぞれのポイントについて説明していきます！

##### (1). `@Configuration`+`@Primary(@Bean)`でDIコンテナに登録する
SpringではアノテーションによるBean定義が可能で、その際に使用する`@Configuration`+`@Bean`でスタブクラスをDIコンテナに登録します。
ただしこの時、本物の`GetAddressApiClient`も登録されているため、**同名のBeanが二つ存在することになりSpringが例外を発生させます。**
それを回避するために**`@Primary`**を使用します！
これにより`@Primary`が付与された方が優先されるようになります。
つまり**常にスタブクラスが使用されるようになる**わけですね。

##### (2). スタブ化対象のクラスを継承したスタブクラスを作る
続いてスタブクラス自体を作ります。
上記で書いたように今回は本物のクラスと同じ型でBean登録させる必要があります。
そのために**本物のクラスを継承させてスタブクラスを作ります**。割と大切です。
※コンストラクタは`super`を使って省略します。

##### (3). メソッドを`@Override`し、戻り値を定義する
あとは戻り値の定義です。
今回で言えば`request`メソッドをオーバーライドし、好きな戻り値を返すようにします。

#### 試しに叩いてみる
bootRunして叩いてみましょう。
<img width="599" alt="無題.png" src="https://qiita-image-store.s3.amazonaws.com/0/188440/75bf526b-07ee-f25f-8cd5-e1e0046a2d5b.png">
スタブに書いたレスポンスが返ってきていますので、
無事スタブ化できているようです！

…しかしここでまた一つ疑問が。
**「このスタブが常に動いちゃうから、逆に本物のクラスが動かないのでは？」**
…その通りです。今度は逆にスタブしか動かないアプリになってしまいました。

そこで**環境ごとに本物とスタブを使い分けられるようにしてみましょう！！**

## 3. スタブを適用させる環境を指定する
**スタブクラスに`@Profile`を付与します**。
これによって**指定された環境でだけスタブクラスがDI登録され、指定されていない環境ではスタブクラスがDI登録されない（≒本物が動く）**ようになります。

``` MockClient.java
@Configuration
@Profile("dev")
public class MockClient {
    // 省略
}
```

上記の場合は`dev環境`でのみスタブクラスとなり、その他環境では本物のClientクラスが動くようになります！

# 最後に
最後までご覧いただきありがとうございました！
意外と調べてみるとこの手の記事が少ないように思いましたので、どなたかの役に立てれば幸いです！

ご指摘など、コメントお待ちしております！