TL;DR
- フロントエンドのテストで、WireMockでモックを用意しただけではうまくいかなかった
- 以下で解決した
- WireMock側
- 自己署名証明書でhttps化
- CORSを受け付けるようにした
- テスト用ブラウザ
- 自己署名証明書をブロックしない設定にした
- WireMock側
はじめに
こんにちは、ピクトリンク事業部開発部サーバサイド開発課、兼ホグワーツ在学中のkitajimaです。
弊社サービスピクトリンクのフロントエンドを対象にしたE2Eテストを実施するためにモックサーバを立てたのですが、その際に手こずった点とその対応を紹介します。
E2Eテストについて
ピクトリンクは、自動テスト用モジュール(以下、テストモジュール)を用いてE2Eテストを実施しています。
Selenideという自動テストフレームワークを利用してブラウザ操作の自動化、検証をし、画面遷移を伴うテストを実現しています。
Selenide.open("https://example.com"); Selenide.sleep(1000); Selenide.$(".hoge").shouldHave(text("fuga"));
Selenideはブラウザ自動化のライブラリであるSeleniumのラッパであり、Seleniumとは違いテストに特化しています。ブラウザ操作のAPI群が隠蔽されているため、上記のように直感的な記述が可能です。
テスト対象のユースケース
現在ピクトリンクでは、中学生ピクトリンク無料サービスというサービスを提供しています。
フロントエンドでは、サービスに関するAPI(以下、サービス用API)が提供する、「サービス申込可否を判定するAPI」にリクエストを投げ、そのレスポンスを元に申込入力ページもしくはsorryページを出しわけています。
sequenceDiagram participant chrome as ブラウザ participant site as ピクトリンクサイト<br>(sp.pictlink.com) participant B as サービス用API<br>(dev.campaign.example.com) chrome->>site: /サービス用ページ site->>chrome: html,js chrome-->>B: 申込可否問い合わせ Note left of B: アカウント情報 B->>chrome: 申込可否判定 chrome->>chrome: 申込入力画面 or sorry画面
今回はフロントエンドのテストのため、外部モジュールであるサービス用APIの振る舞いに依存したくありません。サービス用APIをモックサーバに置き換え、こんなテストが実現できると嬉しいです。
sequenceDiagram participant chrome as テストブラウザ(Selenide) participant site as ピクトリンクサイト(テスト)<br>(xxxx.pictlink.com) participant B as サービス用API<br>(mock.campaign.example.com) chrome->>site: /サービス用ページ site->>chrome: html,js chrome-->>B: 申込可否問い合わせ Note left of B: アカウント情報 B->>chrome: 申込可否判定 chrome->>chrome: 期待するページであるか検証
モックを用意
WireMockを用いてモックサーバを立ててみました。ドキュメントに従ってdocker-compose.yaml
を書いていきます。(XXXX
は任意のポートです)
version: '3.7' services: campaign-service-mock: image: 'wiremock/wiremock:2.32.0' ports: - "XXXX:443" volumes: - ./wiremock:/home/wiremock command: > --verbose --local-response-templating --https-port 443
WireMockは、デフォルトではhttpでのみリクエストを受け付けます。
しかしピクトリンクサイト自体がhttpsであるため、その中にhttpのコンテンツが存在する場合Mixed contentという状態になり、このままではアクセスがブロックされてしまいます。
これを避けるため、WireMockの起動オプションを追加し、https対応しています。
--https-port 443
テスト目的なので、今回はデフォルトで用意される自己署名証明書で良しとしました。
続いて、公式ドキュメントを参照に、JSON形式でモック対象のリクエストを作成します。
{ "request": { "method": "GET", "urlPath": "/available", "headers": { "Authorization": { "matches": "Bearer .+" } } }, "response": { "jsonBody": { "available": true } } }
※以下、登場する*.example.com
はダミーです。
これで、https://campaign.mock.example.com:XXXX/available
にBearerトークン付きでGETリクエストを投げると、期待したレスポンスが返るようになりました。
$ curl -H 'Authorization:Bearer hoge' https://campaign.mock.example.com:XXXX/available {"available": true}
それでは自動テストに組み込んでみましょう。フロントエンドの設定ファイルにて、サービス用APIのURLをモックのものに置き換えました。
- campaignUrl: https://campaign.develop.example.com + campaignUrl: https://campaign.mock.example.com:XXXX
そして自動テストを実行すると・・・
アクセスが弾かれてしまいました。
原因: リクエストがCORSに該当していた
CORS(Cross-Origin Resource Sharing)によりブロックされていました。以下、ブラウザのエラーです。
Access to XMLHttpRequest at 'https://campaign.mock.example.com:XXXX/available' from origin 'https://sp.pictlink.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
サービス用APIはピクトリンクサイトとは別のホスト、つまり異なるオリジンです。 (なんならホストだけでなくポートも異なりますが)
ピクトリンクサイト: https://sp.pictlink.com サービス用APIのモック: https://campaign.mock.example.com:XXXX
そのため、ピクトリンクサイトからサービス用APIのモックへのリクエストは、CORSに相当します。
CORSでは、事前確認用にpreflightリクエストと呼ばれるOPTIONSリクエストをブラウザが自動で発行します。
sequenceDiagram participant chrome as ウェブ文書 from sp.pictlink.com participant B as campaign.mock.example.com chrome->>B: OPTIONS /available B->>chrome: 許可情報
リクエストでは、Access-Control-Request-Method
で送りたいHTTPメソッド、Access-Control-Request-Headers
で送りたいヘッダを指定します。
Access-Control-Request-Method: GET Access-Control-Request-Headers: Authorization
レスポンスでは、Access-Control-Allow-Origin
、Access-Control-Allow-Methods
、Access-Control-Allow-Headers
といったヘッダで、それぞれ許可するオリジン、HTTPメソッド、ヘッダを返します。
また、Access-Control-Max-Age
で、ブラウザがpreflightの結果をキャッシュしてもよい時間を返します。
Access-Control-Allow-Origin: https://sp.pictlink.com Access-Control-Allow-Methods: GET Access-Control-Allow-Headers: Authorization Access-Control-Max-Age: 1800
なお、リクエストが特定のケース(単純リクエスト)に該当する場合はpreflightは発行されません。
つまり、サービス用APIのモックを実現するには、モックがCORSに対応する必要があるわけです。
やったこと① WireMock側の設定
CORSオプションを設定する
WireMockはデフォルトではpreflightリクエストに対応してくれません。
起動オプションに--enable-stub-cors
を追加して解消します。
これにより、モックされた任意のマッピングに対してpreflightリクエストのマッピングも用意してくれるようになります。
以上で修正したdocker-compose.yaml
がこちらです。
version: '3.7' services: campaign-service-mock: image: 'wiremock/wiremock:2.32.0' ports: - "XXXX:443" volumes: - ./wiremock:/home/wiremock command: > --verbose --local-response-templating --enable-stub-cors --https-port 443
やったこと② Selenide側の設定
モック側をhttps対応したものの、ブラウザはデフォルトでは自己署名証明書を不正なものとしてエラーを返します。
WebDriverの
acceptInsecureCerts
という設定をtrueにすることでこれを回避してみます(自動テスト環境でのみ設定すべき内容であり、実際のブラウザでは推奨されるものではありません)。
この辺りは公式ドキュメントにお任せしてしまいますが、ChromeDriverの初期化のタイミングでオプションを以下のように渡せます。
ChromeOptions options = new ChromeOptions(); options.setAcceptInsecureCerts(true); ChromeDriver chromeDriver = new ChromeDriver(options);
いざ実行
フロントエンドからモックサーバに対してアクセスができました。preflightリクエスト、実際のリクエストが並んでいます。
preflightリクエスト 実際のリクエスト
最後に
普段使っているwebフレームワーク(Spring Boot)はCORSに対してよしなに対応してくれているらしく、そのありがたさを感じた事象でした。参考になれば幸いです。