テスト環境

テストとはで説明したように、JavaScript でのテストは基本的に、Error をスローせずに正常に実行されることを確認するコードです。ただし、この定義が単純化されすぎている理由の 1 つは、コードを実行する場所、つまりテスト環境を考慮しないことです。

テスト環境は、テストの実行に使用するランタイム環境(ノードやブラウザなど)とユーザーが利用できる API の 2 つのコンポーネントと考えることができます。

ランタイム環境

Node などのランタイム、または Deno や Bun などの同様のツールは、サーバー側または汎用の JS コードをサポートすることを目的としています。この環境には、DOM 要素や HTML 要素の作成や操作など、ブラウザで期待される API は含まれていません。また、視覚コンポーネントやレンダリング ターゲットの概念(要素だけでなく、これらの要素を CSS でビューポートに視覚的にレンダリングする)も含まれていません。

そのため、たとえば React 要素をレンダリングしてテストできるようにしようとすると、document オブジェクトや window オブジェクトがないため、このような汎用ランタイムは失敗します。

一方、ブラウザ内でテストを実行する場合、これらのランタイムから想定される組み込み API は、ポリフィルまたは追加作業がないと使用できない可能性があります。よくある問題は、ファイルの読み取りと書き込みなどです。テストの一環として、ブラウザ内で import { fs } from 'node:'fs'; を使用してファイルを読み取ることはできません。

この「ウェブ」API と「バックエンド」API の問題は、テストの範囲外です。サーバー部分とクライアント部分の両方を含むコードベースを持つのは扱いづらい可能性があるためです。しかし、これはテスト可能なコードを記述するというアイデアに結びついています。これについては、このコースを通して再度取り上げます。

アルゴリズムまたはビジネス ロジックをテストする

コードによっては、動作にノードまたはブラウザのインポートを必要としないため、テストを行うことができます。これについてはこのコースで後ほど説明しますが、純粋な「ビジネス ロジック」をレンダリングやノード固有のコードから分離するようにコードベースを構造化すると、テストが容易になります。

簡単な例として、ディスクからファイルを読み書きする Node 関数があり、そのプロセスでファイルを変更できます。ディスクの読み取りと書き込みを実行する関数を受け入れるように関数をリファクタリングすることで、どこでもテストできるようになりました。

この場合、サーバーサイドのランタイムまたはブラウザのいずれかで、任意の環境を使用してこのコードをテストできます。テストでは、仮想ファイルをメモリに保存するヘルパーや、プレースホルダ データを返すヘルパーを提供できます。この種のヘルパーは、たとえば fs.writeFileSync が機能しているかどうかの確認は重要ではないため、テストに導入しても問題ありません。コードの一意性やリスクの点に注目してください。

ブラウザ API をエミュレートする

Vitest などの多くのテスト フレームワークには、ブラウザを実行せずにブラウザの API 環境をエミュレートするオプションがあります。Vitest は内部的に JSDOM というライブラリを使用しています。これは、ブラウザを使用するオーバーヘッドが大きい単純なコンポーネント テストに適しています。

エミュレーション ライブラリに共通する特長は、DOM、要素、Cookie など、ブラウザをエミュレートすることはできますが、視覚コンポーネントがないことです。つまり、HTML 要素やその他のプリミティブを処理する命令型の方法を提供しますが、出力を画像や画面にレンダリングすることや、ページ上の要素の位置をピクセル単位で確認することはできません。

ここでも、コンポーネントが React の要素やウェブ コンポーネントなどを表すコンポーネント テストに適しています。この種のコンポーネントは通常、比較的小さな方法で DOM を作成し、操作します。エミュレートされたブラウザは、コンポーネントが意図したとおりに動作することを確認するために十分な機能を提供します。次のセクションでは、Vitest と JSDOM を使用した React コンポーネント テストの例を示します。

ブラウザのエミュレーションは、JSDOM が 2014 年にリリースされたという定評のある手法ですが、実際のブラウザを使用した場合とは異なります。これらの違いは明らかです。たとえば、JSDOM にはレイアウト エンジンが含まれていないため、要素のサイズを確認したり、スワイプなどの複雑な操作をテストしたりする方法はありません。違いは微妙で、不明な場合もあるため、JSDOM ベースのテストは簡潔にし、動作が実際の動作から逸脱するリスクを「時間指定」できるようにすることをおすすめします。

実際のブラウザを制御

ユーザーの目に見える形でコードをテストするには、実際のブラウザを使用することをおすすめします。実際には、ブラウザをサポートするテスト ランタイムは、Node.js 内で「start」を実行しても、実際のブラウザのインスタンスを起動して制御します。

テストの一環としてブラウザを制御すると、ユーザーが使用するときと同じようにブラウザが開き、URL、カスタム HTML およびカスタム JS、またはテストの実行に必要なあらゆるものを読み込んでテストで制御できます。その後、マウスを操作したり、入力ボックスに入力したりするなど、ユーザーとして機能するコードを記述できます。

WebdriverIOWeb Test Runner などの最新のツールは、主要なブラウザをすべて制御でき、複数のインスタンスを同時に実行することもできます。これらのブラウザは、テストランナーと隣接して(たとえば、独自のコンピュータ上や CI アクションの一部として)実行することも、外部の商用サービスにアウトソーシングして実行することもできます。

比較的実績のあるテスト ライブラリ(Vitest や Jest など)には、多くの場合、ブラウザモードがありますが、そのオリジンは Node.js であるため、ブラウザモードが「追加」され、便利な機能が欠落していることが多くあります。たとえば、Vitest はブラウザでのモジュール インポートをモックできません。これは、次のページの例で使用する強力なプリミティブです。

実施中

テストの複雑さが増すと、実際のブラウザを使用することの重要性が増します。

  • DOM の機能をまったく使用しないか、最小限にするテストでは、Node.js や同様のランタイムで使用可能な機能(fetchEventTarget など)であっても、環境は関係ありません。
  • 小規模なコンポーネントのテストには、JSDOM が適しています。
  • より大規模なテスト(エンドツーエンド テストなど、ユーザーのログインとコア アクションの実行をシミュレートできる)は、実際のブラウザで完全に実行する方が合理的です。

このセクションでは理論に重点を置き、どこでテストを実行するかについて、さまざまな視点から説明します。実際には、コードベースでは多くの場合、ニーズとテストツールの機能に基づいて、さまざまな種類のテストに対してさまざまなアプローチを使用します。

理解度をチェックする

エミュレーション レイヤの jsdom がサポート *していない* ブラウザの機能は何ですか?

レイアウト エンジン。
JSDOM はビジュアル ツールではないため、ページ上の要素の位置、解決された CSS 属性、ウェブサイトのレイアウトの他の部分の確認には使用できません。
WebSocket
JSDOM には WebSocket ポリフィルが含まれているため、それを使用するコードは機能します。
requestAnimationFrame
「pretendToBeVisual」フラグを指定すると、実際には何も描画されていなくても 60 fps で「アニメーション」コールバックが呼び出されます。