こんにちは。開発本部でインターンをしている桐澤(@kiririLee)です。今回、「PR TIMES Webクリッピングβ版」 というプロジェクトのフロントエンドで実装されていたE2Eテストに対して、APIリクエストを全てモックするように修正を行ったためその取り組みを紹介したいと思います。
Webクリッピングβ版 について
Webクリッピングβ版とは、WEB上の記事を調査してくれる機能です。キーワードを指定するだけで調査結果を報告をしてくれるため自社の記事掲載状況や社会動向の分析に役立てられます。
フロントエンドで行われていたテスト
Webクリッピングβ版はReactによるSPAで構築されており、主に2種類のテストがあります。関数やReactのコンポーネント単位で行うUnitテストと主要な機能単位で行うE2Eテストです。
今回修正したのは後者の主要な機能単位で書かれたテストです。このテストは、CypressによりQAチームが担保したい機能の要件をまとめたシナリオに沿って書かれていました。テスト内で発生するAPIリクエストは開発環境のバックエンドまで届き、フロントエンドではバックエンドから返ってきたテストケースごとのデータを使用してテストを行っていました。
修正した背景
E2Eテストは全てフロントエンドエンジニアの手で作成されていました。また、フロントエンドからバックエンドまでのQualityを担保したいというよりもフロントエンドのUIのQualityを担保したいという目的が強くありました。実際に、バックエンドの一部はE2Eテスト時にはダミーのデータを返すように設定されていました。
そのような背景からバックエンドなしでもテストができるようにCypressのintercept
コマンドを用いて、APIリクエストをモックするように修正しました。
また、この修正によりバックエンドへの依存がなくなるため、フロントエンドの環境構築だけでテストを回せるようになりGitHub Actions上でもテストが回せるようになる見込みがありました。
修正内容
主に行なったことは以下の3つです。
- APIリクエストのモック
- HTMLのモック
- CIの整備
APIリクエストのモック
E2Eテストでバックエンドまで到達していたAPIリクエストをCypressのintercept
コマンドでモックしました。これで修正後はテスト内で発生したAPIリクエストに対して用意したモックデータが返されるようになりました。
もともとリクエストメソッドとURLをプロパティに持つマッチャーオブジェクトによって部分的にバックエンドからのレスポンスをintercept
していた部分があり、修正する際もそのマッチャーオブジェクトによってリクエストをマッチさせるアイデアを引き継ぎました。
以下のようにマッチャーオブジェクト・レスポンスボディ・エイリアスをプロパティを持つオブジェクトをモックするリクエストごとに作り、bodyの内容をテストケースによって変えるようにしました。
const getMe = {
name: 'get-me',
matcher: <RouteMatcher>{
url: '/path/to/current/login/user',
method: 'GET',
},
body: { name: "hoge" },
alias: function () {
return `@${this.name}`;
},
};
intercept
からこんな感じで使います。
cy.intercept(getMe.matcher, {
body: getMe.body,
}).as(getMe.name);
cy.visit('/');
cy.await(getMe.alias())
HTMLとJSのホスティング
ページに訪れた際に取得されるアセットもシンプルなローカルサーバーから取得するようにモックしました。E2Eテストでは開発環境のバックエンドサーバーからHTMLを取得していましたが、この環境だとプロキシサーバーやDBなどの依存が重かったため、修正後のテストではHTMLとJSのみを配信するローカルサーバーを立ててそこから取得するようにしました。
Webクリッピングβ版 はReactのSPAなのでid付きのdivタグとJSを読み込むscriptタグのみが書かれたほぼ空のHTMLを用意し、vercel/serveを使ってJSと一緒に配信するようにしました。
scriptタグから読み込まれるJSはビルドされたReactのアプリケーションです。以下のようにSPAの遷移をモックするためにSPAで遷移するURLに対して同じHTMLを返すようにserveを設定しました。
{
// ここにビルド済みのバンドルファイルを置いておき、htmlのscriptタグから参照します。
"public": "../dist",
"headers": [
// ...省略...
],
"rewrites": [
{
// spa/root/path 配下のすべてのリクエストに対してdestinationのhtmlが返されます。
"source": "/spa/root/path/**",
"destination": "/spa/root/path/index.html"
}
]
}
また、E2Eテストがcypress.config.ts
のbaseUrl
を使用して書かれていたため、修正後はbaseUrl
をローカルサーバーのURLに変更することで各テスト内でページに訪れた際に取得されるHTMLがモックしたものに置き換わるようにしました。以下にbaseUrl
の設定例を載せます。
import { defineConfig } from 'cypress';
export default defineConfig({
// ...省略...
e2e: {
baseUrl: 'http://localhost:9090',
specPattern: 'integration/**/*.cy.{js,jsx,ts,tsx}',
},
});
修正後はテストを実行する際にテスト用のローカルサーバーを立ち上げる必要がありますが、これにはbahmutov/start-server-and-testというパッケージを使いました。このパッケージはローカルサーバーが立ち上がったことを確認してからテストを実行してくれます。
CIの整備
上記の作業によりフロントエンドだけでテストが完結する環境が整いました。そして開発用のバックエンドサーバーへの依存がなくなったためGitHub Actions上でコミット毎に修正後のテストが回せるようになりました。
GitHub Actions上では、cypress-io/github-action@v4.2.0 というアクションを使うことで、with.start
にローカルサーバーの立ち上げコマンドを指定するだけでstart-server-and-test
で行っていたことを自動的にやってくれます。
// ...省略...
integration-test:
// ...省略...
steps:
- name: Checkout
uses: actions/checkout@master
- name: Setup
uses: setup/anything
- name: Cypress run
uses: cypress-io/github-action@v4.2.0
with:
build: yarn build
start: yarn serve
// ...省略...
経過観察
元々あったE2Eテストを修正してから約1ヶ月経ちましたが、現在はUnitテストで書くには範囲が大きいテストやブラウザ機能のモックが大変な時に今回作ったテスト環境が使われています。
修正後のテストはUnitテストなど他のGitHub Actionsのジョブと比較して一番実行時間が長くなっていたり、intercept
によるリクエストのモックが冗長になっていたりすることが改善点としてあります。これに対してはWebクリッピングβ版とは別のフロントエンドプロジェクトでCypress以外のテストツールを試す取り組みが既に行われています。
最後に
プロダクトの品質につながるテストを書くためにフロントエンドチームでは以下の会議を中心にテストについて活発に議論が行われています。現在はプロジェクトごとに試行錯誤をしている段階で今回のE2Eテストの修正もその試みの内の一つです。
E2Eテストを修正したことでCI上で開発環境のバックエンドに依存しないテストが回るようになりましたが、経過観察で挙げた改善点などを回収することで今後さらに良いテスト環境が整う可能性があります。今回のような取り組みを続けていくことでプロジェクトに適した環境を整えていきたいです。
また、僕自身はテストの運用面だけでなく書き方自体も経験が不足している状態ではありますがチームで試行錯誤していることを通して学んでいきたいです。