Skip to main content
3 月 5 日~ 6 日にサンフランシスコで開催される TDX (Salesforce+ でも配信) で「Developer Conference for the AI Agent Era (AI エージェント時代に向けた開発者向けカンファレンス)」にぜひご参加ください。お申し込みはこちら

モックオブジェクトとスタブオブジェクトを使用する

学習の目的

  • モックとスタブが何かを説明する。
  • モックやスタブを使用する状況を説明する。
  • モックやスタブを使用して単体テストを記述する。
メモ

メモ

日本語で受講されている方へ
Challenge は日本語の Trailhead Playground で開始し、かっこ内の翻訳を参照しながら進めていってください。Challenge での評価は英語データを対象に行われるため、英語の値のみをコピーして貼り付けるようにしてください。日本語の組織で Challenge が不合格だった場合は、(1) この手順に従って [Locale (地域)] を [United States (米国)] に切り替え、(2) [Language (言語)] を [English (英語)] に切り替えてから、(3) [Check Challenge (Challenge を確認)] ボタンをクリックしてみることをお勧めします。

翻訳版 Trailhead を活用する方法の詳細は、自分の言語の Trailhead バッジを参照してください。

パターン

単体テストの分野において、モックとスタブは高度なトピックです。ただし、テストを簡単に記述して理解し、管理するうえで、この 2 つは極めて有用です。また、テストするコードを、コードベースの他の部分の変更から切り離します。次の動画で、モックとスタブの概要をご覧ください。

この単元では、HttpMock とカスタムスタブオブジェクトを作成して使用します。通常はモックオブジェクトと総称され、どちらも同じ目的を果たします。つまり、オブジェクトの実際のインスタンスの代用オブジェクトです。代用であるため、その機能を上書きして、自ら選んだデータを返すことができます。 

厳密に言うと、モックオブジェクトとスタブオブジェクトは若干異なります。モックオブジェクトはオブジェクトレベルで機能します。スタブは個々のメソッドに取って代わります。Lightning プラットフォームでは、開発者がプラットフォームインターフェースを拡張して、モックとスタブを記述します。たとえば、HTTP 応答モックを作成する場合は、HTTPCalloutMock インターフェースを拡張するクラスを作成します。(この点は、後ほど詳述します。)モックオブジェクトを作成して、テストするコードを、組織のテストしない他のコード (サードパーティのコードやサービス、現在テストしているもの以外のクラスなど) から切り離します。 

では、モックオブジェクトとスタブオブジェクトのユースケースを見てみましょう。モックオブジェクトには典型的なユースケースが 2 つあります。1 つ目は特殊なケースです。Apex からサードパーティの Web サービスにコールアウトを実行するときに必ず使用します。その場合は、HTTP コールアウトをモックする必要があります。2 つ目のユースケースのほうが一般的です。別のオブジェクトの内部状態や実装に連動するコードをテストするときは、常にこのユースケースを目にします。このような状況では、テスト時にスタブオブジェクトを使用すると非常に便利です。 

パターンの点から、この 2 つのユースケースがどのようなものか見てみましょう。

モックオブジェクトのパターン

以下は、HttpCalloutMock のパターンです。

  1. HttpCalloutMock インターフェースを実装するクラスを作成します。HTTPMockFactory を例に挙げます。
  2. テストデータを作成するか読み込みます。
  3. HTTPMockFactory のインスタンスを作成します。たとえば、HTTPMockFactory mockInstance = new HTTPMockFactory() とします。
  4. Test.setMock(mockInstance) をコールして、前のステップで作成したモックオブジェクトのインスタンスを渡します。
  5. Test.startTest(); をコールします。
  6. コールアウトを行うコードを実行します。
  7. Test.stopTest(); をコールします。
  8. アサーションを実行して、コードが期待どおり機能することを確認します。

スタブオブジェクトのパターン

スタブオブジェクトを使用する場合も、パターンはよく似ています。

  1. テストデータを作成するか読み込みます。
  2. StubProvider インターフェースを実装するクラスのインスタンスを作成します。
  3. Test.startTest(); をコールします。
  4. テストするコードをコールして、スタブインスタンスを渡します。
  5. Test.stopTest(); をコールします。
  6. アサーションを実行して、コードが期待どおり機能することを確認します。

HTTPMockFactory クラスを作成する

新しい HTTPCalloutMock クラスを作成し、テストのコンテキストで使用するところを見てみましょう。

  1. VS Code を開きます。
  2. [Explorer (エクスプローラー)] サイドバーで、classes フォルダーを右クリックし、[SFDX: Create Apex Class (SFDX: Apex クラスを作成)] を選択します。
  3. このクラスに HTTPMockFactory と名前を付け、デフォルトのディレクトリを受け入れます。
  4. デフォルトの内容を次のコードに置き換えます。
    @IsTest
    public class HTTPMockFactory implements HttpCalloutMock {
      protected Integer code;
      protected String status;
      protected String body;
      protected Map<String, String> responseHeaders;
      public HTTPMockFactory(
        Integer code,
        String status,
        String body,
        Map<String, String> responseHeaders
      ) {
        this.code = code;
        this.status = status;
        this.body = body;
        this.responseHeaders = responseHeaders;
      }
      public HTTPResponse respond(HTTPRequest req) {
        HttpResponse res = new HttpResponse();
        for(String key : this.responseHeaders.keySet()) {
          res.setHeader(key, this.responseHeaders.get(key));
        }
        res.setBody(this.body);
        res.setStatusCode(this.code);
        res.setStatus(this.status);
        return res;
      }
    }
  5. [File (ファイル)] > [Save (保存)] をクリックします。
  6. 作業中のファイルを右クリックして、[SFDX: Deploy Source To Org (SFDX: 組織にソースをリリース)] を選択します。

コードのポイント

このクラスのコンストラクターが、response メソッドが返すパラメーターを受け入れます。データの testFactory と同様に、このファクトリを使用すると、テストの一環としてモックをその場で定義できます。 

テスト

このモジュールの単元 1 でインストールしたパッケージに、ExternalSearch.cls というクラスが付属します。このクラスは検索文字列を受け入れ、その Web 検索を実行します。モックファクトリを使用して、その単体テストを記述してみましょう。 

  1. VS Code を開きます。
  2. [Explorer (エクスプローラー)] サイドバーで、classes フォルダーを右クリックし、[SFDX: Create Apex Class (SFDX: Apex クラスを作成)] を選択します。
  3. このクラスに ExternalSearchTests と名前を付けます。
  4. その内容を次のコードに置き換えます。
    @IsTest
    private class ExternalSearchTests {
      @IsTest
      static void testPositiveMocking() {
        // GIVEN
        HTTPMockFactory mock = new HTTPMockFactory(
          200,
          'OK',
          'I found it!',
          new Map<String, String>()
        );
        Test.setMock(HttpCalloutMock.class, mock);
        // WHEN
        Test.startTest();
          String result = ExternalSearch.googleIt('epic search');
        Test.stopTest();
        // THEN
        Assert.areEqual('I found it!', result, 'Expected to receive mock response');
      }
    }
  5. [File (ファイル)] > [Save (保存)] をクリックします。
  6. 作業中のファイルを右クリックして、[SFDX: Deploy Source To Org (SFDX: 組織にソースをリリース)] を選択します。
  7. testPositiveMocking メソッドに表示される [Run Test (テストを実行)] ボタンをクリックします。

コードのポイント

このテストの要は、Test.setMock() をコールすることです。このコールにより、コードが実際にコールアウトを行うことがなく、代わりに指定した HttpResponse が返されます。この例では、googleIt メソッドの戻り値が「I found it! (見つかりました!)」になります。この戻り値をモックファクトリに挿入する機能により、ポジティブテストとネガティブテストの両方を簡単に記述できます。 

スタブ化

スタブもモックに似ていますが、より柔軟です。 

  1. VS Code を開きます。
  2. [Explorer (エクスプローラー)] サイドバーで、classes フォルダーをクリックします。
  3. [OpportunityDiscount] クラスを選択します。これがテストするコードです。

getTotalDiscount() メソッドが、取引先の優先度が高いかどうかに基づいて、商談の割引合計を算定します。isHighPriority() の実装は時間の経過と共に変化する可能性があるため、getTotalDiscount() メソッドのテスト時に内部の動作と連動しないようにします。 

このシナリオは、AccountWrapper クラスのスタブを作成する最適な状況です。スタブの有用性を示す簡単なテストを記述しましょう。

  1. VS Code を開きます。
  2. [Explorer (エクスプローラー)] サイドバーで、classes フォルダーを右クリックし、[SFDX: Create Apex Class (SFDX: Apex クラスを作成)] を選択します。 
  3. このクラスに OpportunityDiscountTests と名前を付け、デフォルトのディレクトリを受け入れます。
  4. その内容を次のコードに置き換えます。
    @IsTest
    private class OpportunityDiscountTests {
      @IsTest
      static void testPositiveStubbingLowPriority() {
        // GIVEN
        AccountWrapper mockAccountWrapper = (AccountWrapper)
        Test.createStub(AccountWrapper.class, new AccountWrapperMock());
        OpportunityDiscount od = new OpportunityDiscount(mockAccountWrapper);
        // WHEN
        Test.startTest();
          Decimal result = od.getTotalDiscount();
        Test.stopTest();
        // THEN
        Assert.areEqual(.1, result, 'Expected to get .1');
      }
      @IsTest
      static void testPositiveStubbingHighPriority() {
        // GIVEN
        AccountWrapperMock.isHighPriorityReturn = true;
        AccountWrapper mockAccountWrapper = (AccountWrapper) Test.createStub(
          AccountWrapper.class, 
          new AccountWrapperMock()
        );
        OpportunityDiscount od = new OpportunityDiscount(mockAccountWrapper);
        // WHEN
        Test.startTest();
          Decimal result = od.getTotalDiscount();
        Test.stopTest();
        // THEN
        Assert.areEqual(.25, result, 'Expected to get .25');
      }
    }
  5. [File (ファイル)] > [Save (保存)] をクリックします。
  6. 作業中のファイルを右クリックして、[SFDX: Deploy Source To Org (SFDX: 組織にソースをリリース)] を選択します。
  7. クラスの上部に表示される [Run Test (テストを実行)] ボタンをクリックします。

コードのポイント

この 2 つのテストメソッドはほぼ同じです。構造上、テストは AccountWrapper のモックインスタンスを作成して機能します。この取引先ラッパーは、実際の AccountWrapper オブジェクトの代わりに OpportunityDiscount オブジェクトに挿入されます。その結果、isHighPriority() メソッドがコールされたときに AccountWrapper オブジェクトがどのように応答するかを正確に把握できます。 

主な違いは、2 つ目のテストメソッドの最初の行で、AccountWrapperMock に静的変数を設定することです。これは巧妙なやり方で、パラメーターがないスタブメソッドの動作を操作する際に役立ちます。スタブ化するメソッドがパラメーターを受け入れない場合は、スタブクラスで定義した静的変数を使用して、さまざまなテストでスタブの動作を意図的に変更できます。 

まとめ

このモジュールには情報がぎっしり詰まっています。テストデータの統合、ポジティブテストとネガティブテスト、権限テスト、モックとスタブについて説明しました。こうしたパターンやツールを組み合わせれば、コードベースの生産性を高めるテストが確立されます。また、テストを記述することでコードをどのように記述するかが方向付けられるため、開発者として向上することができます。こうした概念や実践法を身に付ける最善の方法は、テストをどんどん記述していくことです。

ハンズオン Challenge

+500 ポイント

準備を始めましょう

この 単元 は各自のハンズオン組織で実行します。[起動] をクリックして開始するか、組織の名前をクリックして別の組織を選びます。

あなたの Challenge

Write tests using mocks and stubs
Not every call to a third-party service succeeds. So, we need to test both success and failure responses. Write a unit test to cover the ExternalSearch class handling of response codes higher than 200.
  • Add a unit test method called testNegativeMocking to ExternalSearchTests.cls that uses the HTTPMockFactory class to return a 500 response.
  • Make sure you have 100% code coverage on the ExternalSearch class.
Salesforce ヘルプで Trailhead のフィードバックを共有してください。

Trailhead についての感想をお聞かせください。[Salesforce ヘルプ] サイトから新しいフィードバックフォームにいつでもアクセスできるようになりました。

詳細はこちら フィードバックの共有に進む