Advanced 35 min read

Mocking HTTP Callouts

By the end of this lesson, you'll be able to:

  • Explain why a real HTTP callout can't run inside a test
  • Implement HttpCalloutMock to simulate an external response
  • Test both a successful and a failed callout response

Prerequisites: "Test Data Factories"

Why real callouts can't run in a test

Apex tests explicitly cannot make real HTTP callouts — attempting one throws a CalloutException. This is deliberate: tests need to run reliably and repeatably regardless of whether an external system is actually available, fast, or even still exists months later, and they should never have side effects on a real third-party service (a later Integration module covers callouts fully; this lesson focuses purely on testing them).

Implementing a mock response

@isTest
public class ShippingRateMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest request) {
        HttpResponse response = new HttpResponse();
        response.setStatusCode(200);
        response.setBody('{"rate": 45.50}');
        return response;
    }
}
  1. @isTest — marks the class as test-only.
  2. public class ShippingRateMock implements HttpCalloutMock { — a class implementing HttpCalloutMock defines exactly what response the mocked callout should return.
  3. public HttpResponse respond(HttpRequest request) { — the one method the interface requires.
  4. HttpResponse response = new HttpResponse(); — builds a fake response.
  5. response.setStatusCode(200); — a successful status.
  6. response.setBody('{"rate": 45.50}'); — a fake JSON body, entirely fabricated by the test.
  7. return response; — hands it back, with no real network request ever made.
  8. } — closes the method.
  9. } — closes the class.

Using the mock in a test

@isTest
static void getShippingRateReturnsParsedRate() {
    Test.setMock(HttpCalloutMock.class, new ShippingRateMock());

    Test.startTest();
    Decimal rate = ShippingService.getShippingRate('12345');
    Test.stopTest();

    System.assertEquals(45.50, rate);
}
  1. @isTest — marks the test method.
  2. static void getShippingRateReturnsParsedRate() { — the test.
  3. Test.setMock(HttpCalloutMock.class, new ShippingRateMock()); — tells Apex: any real callout attempted during this test should be intercepted and answered by ShippingRateMock instead.
  4. (blank line)
  5. Test.startTest(); — marks the boundary.
  6. Decimal rate = ShippingService.getShippingRate('12345'); — runs its actual logic unmodified, just receiving a fake response instead of a real one.
  7. Test.stopTest(); — marks the end.
  8. (blank line)
  9. System.assertEquals(45.50, rate); — confirms the response was parsed correctly.
  10. } — closes the test method.

Testing a failure response too

@isTest
public class ShippingRateFailureMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest request) {
        HttpResponse response = new HttpResponse();
        response.setStatusCode(500);
        response.setBody('Internal Server Error');
        return response;
    }
}

@isTest
static void getShippingRateHandlesServerError() {
    Test.setMock(HttpCalloutMock.class, new ShippingRateFailureMock());

    Test.startTest();
    Decimal rate = ShippingService.getShippingRate('12345');
    Test.stopTest();

    System.assertEquals(null, rate); // however the service is designed to handle a failure
}
  1. @isTest — marks the class.
  2. public class ShippingRateFailureMock implements HttpCalloutMock { — exactly Module 31's "Testing Exceptions" lesson pattern, extended to callouts — a second mock class.
  3. public HttpResponse respond(HttpRequest request) { — the required method.
  4. HttpResponse response = new HttpResponse(); — builds a fake response.
  5. response.setStatusCode(500); — this time, simulating a server error.
  6. response.setBody('Internal Server Error'); — a fake error body.
  7. return response; — hands it back.
  8. } — closes the method.
  9. } — closes the class.
  10. (blank line)
  11. @isTest — marks the test method.
  12. static void getShippingRateHandlesServerError() { — verifies the code's failure-handling path, not just its happy path.
  13. Test.setMock(HttpCalloutMock.class, new ShippingRateFailureMock()); — swaps in the failure mock.
  14. (blank line)
  15. Test.startTest(); — marks the boundary.
  16. Decimal rate = ShippingService.getShippingRate('12345'); — runs against the simulated failure.
  17. Test.stopTest(); — marks the end.
  18. (blank line)
  19. System.assertEquals(null, rate); — however the service is designed to handle a failure — a scenario that would be genuinely difficult to reliably reproduce against a real external system on demand.
  20. } — closes the test method.

Exercise

Write a mock class OrderStatusMock returning a 200 response with the body '{"status": "shipped"}', then a test method using it.

Show hint

implements HttpCalloutMock, then Test.setMock(HttpCalloutMock.class, new OrderStatusMock());

Salesforce: ○ Not connected Connect
APEX

Execution Results

Run your code to see the result.

Mocking HTTP Callouts Quiz

1. Why does Apex forbid real HTTP callouts during tests?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

Apex forbids real HTTP callouts during tests — HttpCalloutMock lets a test simulate exactly what an external system would have returned, without ever actually calling it.