進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

Use Mocks and Stub Objects

Learning Objectives

  • Explain what a mock and a stub are.
  • Describe when to use a mock and a stub.
  • Write unit tests using mocks and stubs.

The Pattern

Mocks and stubs are more advanced topics in the realm of unit testing. However, they’re incredibly useful for making tests easier to write, understand, and maintain. They also insulate the code you’re testing from changes to other parts of your code base. Check out this video for an introduction to mocks and stubs.


In this unit, we create and use an HttpMock and a custom stub object. Often collectively referred to as mock objects, they serve the same purpose: they are fake objects that stand in for real instances of objects. Because they’re fake, we can override their functionality and return the data of our choosing. 

Technically, a mock and a stub object are slightly different. Mock objects work on the object level. Stubs replace individual methods. On the Lightning Platform, developers write mocks and stubs by extending platform interfaces. For example, to create an HTTP response mock, you create a class that extends the HTTPMock interface. (We look at this in more detail in just a moment.) We create mock objects to isolate the code we’re testing from any code in our org that we’re not testing—for example, third-party code, services, and even classes other than the one we’re actively testing. 

So what are the use cases for mock and stub objects? There are two classic use cases for mock objects. The first is specialized. You find it whenever you’re making a callout from Apex to a third-party web service. In that case, you need to mock the HTTP callout. The second use case is more generic. You come across this use case whenever you’re testing code that relies on another object’s internal state or implementation. In these situations, it’s incredibly useful to use a stub object while testing. 

Let’s see what these two use cases look like from a pattern perspective.

Mock Object Pattern

Here’s the pattern for HttpCalloutMock.

  1. Create a class that implements the HttpCalloutMock interface. For example, MyCalloutMock.
  2. Create or load your test data.
  3. Create an instance of MyCalloutMock. For example,  MyCalloutMock mockInstance = new MyCalloutMock().
  4. Call Test.setMock(mockInstance), passing in your mock object instance that you created in the previous step.
  5. Call Test.startTest();
  6. Execute your code that makes a callout.
  7. Call Test.stopTest();
  8. Make assertions to ensure your code functions as expected.

Stub Object Pattern

When you’re using stub objects, the pattern is similar.

  1. Create or load your test data.
  2. Create an instance of a class that implements the StubProvider interface.
  3. Call Test.startTest();
  4. Call the code you want to test, passing in your stub instance.
  5. Call Test.stopTest();
  6. Make assertions to ensure your code functions as expected.

Create an HTTPCalloutMock Class

Let’s look at creating a new HTTPCalloutMock class and using it in a test context.

  1. Click Setup icon and select Developer Console.
  2. Select File > New > Apex Class.
  3. Name the class HTTPMockFactory and replace the default contents with the following code.
    @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;
      }
    }
  4. Click File > Save.

Code Highlights

This class’s constructor accepts parameters that the respond method passes back. Like our testFactory for data, this factory allows us to define the mock on the fly, as part of our test. 

The Test

In the package you installed in unit 1 of this module is a class called ExternalSearch.apxc. It accepts a search string and executes a web search of it for you. Let's write a unit test for it with our mock factory. 

  1. Click Setup icon and select Developer Console.
  2. Select File > New > Apex Class.
  3. Name the class ExternalSearch_Tests.
  4. Replace its contents with the following code.
    @isTest
    private class ExternalSearch_Tests {
      @isTest static void test_method_one() {
        HttpMockFactory mock = new HttpMockFactory(200, 'OK', 'I found it!', new Map<String,String>());
        Test.setMock(HttpCalloutMock.class, mock);
        String result;
        Test.startTest();
          result = ExternalSearch.googleIt('epic search');
        Test.stopTest();
        system.assertEquals('I found it!', result); 
      }
    }
  5. Click File > Save.
  6. Select ExternalSearch_Tests, then select Test_method_one.
  7. Click Run.

Code Highlights

The key to this test is to call Test.setMock(). This call ensures your code never actually makes a callout, returning the HttpResponse you specify instead. In this case, that means the return value of our googleIt method is “I found it!” This ability to inject the return value into the mock factory lets you easily write both positive and negative tests. 

Stubbing

Stubbing is similar to mocking, but it’s more flexible. 

  1. Click Setup icon  and select Developer Console.
  2. Select File > Open > OpportunityDiscount.apxc.

The getTotalDiscount() method determines the total discount for an opportunity based on whether the account is high priority. Because the implementation of isHighPriority() may change over time, we don’t want to be dependent on how it works internally when we test the getTotalDiscount() method. 

This is the perfect situation to create a stub of the AccountWrapper class. Let’s write a quick test to demonstrate that.

  1. In the Developer Console, select File > New > Apex Class.
  2. Name the class OpportunityDiscount_Tests.
  3. Replace its contents with the following code.
    @isTest
    private class OpportunityDiscount_tests {
      @isTest static void test_positive_low_priority() {
        AccountWrapper mockAccountWrapper = (AccountWrapper) Test.createStub(AccountWrapper.class, new AccountWrapperMock());
        OpportunityDiscount od = new OpportunityDiscount(mockAccountWrapper);
        Decimal result;
        Test.startTest();
          result = od.getTotalDiscount();
        Test.stopTest();
        System.assertEquals(.1, result, 'expected to get .1');
      }
      @isTest static void test_positive_high_priority() {
        AccountWrapperMock.isHighPriorityReturn = true;
        AccountWrapper mockAccountWrapper = (AccountWrapper) Test.createStub(AccountWrapper.class, new AccountWrapperMock());
        OpportunityDiscount od = new OpportunityDiscount(mockAccountWrapper);
        Decimal result;
        Test.startTest();
          result = od.getTotalDiscount();
        Test.stopTest();
        System.assertEquals(.25, result, 'expected to get .25');
      }
    }
  4. Click File > Save.
  5. Click Test > New Run.
  6. Select OpportunityDiscount_tests, then select test_positive_low_priority.
  7. Click Run.

Code Highlights

These two test methods are almost identical. Structurally, the tests work by creating a mock instance of AccountWrapper. That account wrapper is injected into the OpportunityDiscount object in place of an actual AccountWrapper object. As a result, we know exactly how the AccountWrapper object responds when the isHighPriority() method is called. 

The key difference is on the first line of the second test method, where you set a static variable on the AccountWrapperMock. This is a neat trick that’s useful for manipulating the behavior of your zero-parameter stubbed methods. When the methods you’re stubbing do not accept parameters, you can use a static variable defined in your stub class to intentionally alter the behavior of the stub in multiple tests. 

Conclusion

This module is jam-packed with information. We’ve covered why we test, positive and negative testing, permissions testing, mocking and stubbing, and testing Lightning components. Together, these patterns and tools establish tests that are a productive part of the code base. They also can help you become a better developer, since writing tests starts to shape how you write your code. Remember that the best way to internalize these ideas and practices is to write more tests!