モックオブジェクトとスタブオブジェクトを使用する
学習の目的
- モックとスタブが何かを説明する。
- モックやスタブを使用する状況を説明する。
- モックやスタブを使用して単体テストを記述する。
パターン
単体テストの分野において、モックとスタブは高度なトピックです。ただし、テストを簡単に記述して理解し、管理するうえで、この 2 つは極めて有用です。また、テストするコードを、コードベースの他の部分の変更から切り離します。次の動画で、モックとスタブの概要をご覧ください。
この単元では、HttpMock
とカスタムスタブオブジェクトを作成して使用します。通常はモックオブジェクトと総称され、どちらも同じ目的を果たします。つまり、オブジェクトの実際のインスタンスの代用オブジェクトです。代用であるため、その機能を上書きして、自ら選んだデータを返すことができます。
厳密に言うと、モックオブジェクトとスタブオブジェクトは若干異なります。モックオブジェクトはオブジェクトレベルで機能します。スタブは個々のメソッドに取って代わります。Lightning プラットフォームでは、開発者がプラットフォームインターフェースを拡張して、モックとスタブを記述します。たとえば、HTTP 応答モックを作成する場合は、HTTPCalloutMock
インターフェースを拡張するクラスを作成します。(この点は、後ほど詳述します。)モックオブジェクトを作成して、テストするコードを、組織のテストしない他のコード (サードパーティのコードやサービス、現在テストしているもの以外のクラスなど) から切り離します。
では、モックオブジェクトとスタブオブジェクトのユースケースを見てみましょう。モックオブジェクトには典型的なユースケースが 2 つあります。1 つ目は特殊なケースです。Apex からサードパーティの Web サービスにコールアウトを実行するときに必ず使用します。その場合は、HTTP コールアウトをモックする必要があります。2 つ目のユースケースのほうが一般的です。別のオブジェクトの内部状態や実装に連動するコードをテストするときは、常にこのユースケースを目にします。このような状況では、テスト時にスタブオブジェクトを使用すると非常に便利です。
パターンの点から、この 2 つのユースケースがどのようなものか見てみましょう。
モックオブジェクトのパターン
以下は、HttpCalloutMock
のパターンです。
HttpCalloutMock
インターフェースを実装するクラスを作成します。HTTPMockFactory
を例に挙げます。- テストデータを作成するか読み込みます。
HTTPMockFactory
のインスタンスを作成します。たとえば、HTTPMockFactory mockInstance = new HTTPMockFactory()
とします。Test.setMock(mockInstance)
をコールして、前のステップで作成したモックオブジェクトのインスタンスを渡します。Test.startTest();
をコールします。- コールアウトを行うコードを実行します。
Test.stopTest();
をコールします。- アサーションを実行して、コードが期待どおり機能することを確認します。
スタブオブジェクトのパターン
スタブオブジェクトを使用する場合も、パターンはよく似ています。
- テストデータを作成するか読み込みます。
StubProvider
インターフェースを実装するクラスのインスタンスを作成します。Test.startTest();
をコールします。- テストするコードをコールして、スタブインスタンスを渡します。
Test.stopTest();
をコールします。- アサーションを実行して、コードが期待どおり機能することを確認します。
HTTPMockFactory クラスを作成する
新しい HTTPCalloutMock
クラスを作成し、テストのコンテキストで使用するところを見てみましょう。
- VS Code を開きます。
- [Explorer (エクスプローラー)] サイドバーで、
classes
フォルダーを右クリックし、[SFDX: Create Apex Class (SFDX: Apex クラスを作成)] を選択します。 - このクラスに
HTTPMockFactory
と名前を付け、デフォルトのディレクトリを受け入れます。 - デフォルトの内容を次のコードに置き換えます。
- [File (ファイル)] > [Save (保存)] をクリックします。
- 作業中のファイルを右クリックして、[SFDX: Deploy Source To Org (SFDX: 組織にソースをリリース)] を選択します。
コードのポイント
このクラスのコンストラクターが、response メソッドが返すパラメーターを受け入れます。データの testFactory と同様に、このファクトリを使用すると、テストの一環としてモックをその場で定義できます。
テスト
このモジュールの単元 1 でインストールしたパッケージに、ExternalSearch.cls
というクラスが付属します。このクラスは検索文字列を受け入れ、その Web 検索を実行します。モックファクトリを使用して、その単体テストを記述してみましょう。
- VS Code を開きます。
- [Explorer (エクスプローラー)] サイドバーで、
classes
フォルダーを右クリックし、[SFDX: Create Apex Class (SFDX: Apex クラスを作成)] を選択します。 - このクラスに
ExternalSearchTests
と名前を付けます。 - その内容を次のコードに置き換えます。
- [File (ファイル)] > [Save (保存)] をクリックします。
- 作業中のファイルを右クリックして、[SFDX: Deploy Source To Org (SFDX: 組織にソースをリリース)] を選択します。
testPositiveMocking
メソッドに表示される [Run Test (テストを実行)] ボタンをクリックします。
コードのポイント
このテストの要は、Test.setMock()
をコールすることです。このコールにより、コードが実際にコールアウトを行うことがなく、代わりに指定した HttpResponse が返されます。この例では、googleIt
メソッドの戻り値が「I found it! (見つかりました!)」になります。この戻り値をモックファクトリに挿入する機能により、ポジティブテストとネガティブテストの両方を簡単に記述できます。
スタブ化
スタブもモックに似ていますが、より柔軟です。
- VS Code を開きます。
- [Explorer (エクスプローラー)] サイドバーで、
classes
フォルダーをクリックします。 - [OpportunityDiscount] クラスを選択します。これがテストするコードです。
getTotalDiscount()
メソッドが、取引先の優先度が高いかどうかに基づいて、商談の割引合計を算定します。isHighPriority()
の実装は時間の経過と共に変化する可能性があるため、getTotalDiscount()
メソッドのテスト時に内部の動作と連動しないようにします。
このシナリオは、AccountWrapper
クラスのスタブを作成する最適な状況です。スタブの有用性を示す簡単なテストを記述しましょう。
- VS Code を開きます。
- [Explorer (エクスプローラー)] サイドバーで、
classes
フォルダーを右クリックし、[SFDX: Create Apex Class (SFDX: Apex クラスを作成)] を選択します。 - このクラスに
OpportunityDiscountTests
と名前を付け、デフォルトのディレクトリを受け入れます。 - その内容を次のコードに置き換えます。
- [File (ファイル)] > [Save (保存)] をクリックします。
- 作業中のファイルを右クリックして、[SFDX: Deploy Source To Org (SFDX: 組織にソースをリリース)] を選択します。
- クラスの上部に表示される [Run Test (テストを実行)] ボタンをクリックします。
コードのポイント
この 2 つのテストメソッドはほぼ同じです。構造上、テストは AccountWrapper
のモックインスタンスを作成して機能します。この取引先ラッパーは、実際の AccountWrapper
オブジェクトの代わりに OpportunityDiscount
オブジェクトに挿入されます。その結果、isHighPriority()
メソッドがコールされたときに AccountWrapper
オブジェクトがどのように応答するかを正確に把握できます。
主な違いは、2 つ目のテストメソッドの最初の行で、AccountWrapperMock
に静的変数を設定することです。これは巧妙なやり方で、パラメーターがないスタブメソッドの動作を操作する際に役立ちます。スタブ化するメソッドがパラメーターを受け入れない場合は、スタブクラスで定義した静的変数を使用して、さまざまなテストでスタブの動作を意図的に変更できます。
まとめ
このモジュールには情報がぎっしり詰まっています。テストデータの統合、ポジティブテストとネガティブテスト、権限テスト、モックとスタブについて説明しました。こうしたパターンやツールを組み合わせれば、コードベースの生産性を高めるテストが確立されます。また、テストを記述することでコードをどのように記述するかが方向付けられるため、開発者として向上することができます。こうした概念や実践法を身に付ける最善の方法は、テストをどんどん記述していくことです。