Advanced 30 min read

Test with HttpCalloutMock

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

  • Test the success path with a mock returning a valid response
  • Test the non-200 error path with a mock
  • Test the CalloutException path using Test.setMock with a throwing mock

Prerequisites: "Handle Errors and Timeouts"

Testing the success path

@isTest
public class CurrencyApiSuccessMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest request) {
        HttpResponse response = new HttpResponse();
        response.setStatusCode(200);
        response.setBody('{"convertedAmount": 5.43, "rate": 0.054, "timestamp": "2026-08-06T10:00:00Z"}');
        return response;
    }
}

@isTest
private class CurrencyConversionServiceTest {
    @isTest
    static void convertReturnsCorrectAmount() {
        Test.setMock(HttpCalloutMock.class, new CurrencyApiSuccessMock());

        Test.startTest();
        Decimal result = new CurrencyConversionService().convert(100, 'USD');
        Test.stopTest();

        System.assertEquals(5.43, result);
    }
}

Exactly Module 36's closing lesson, applied to this real service — a specific fabricated response, a specific expected value, both matching Lesson 1's contract precisely.

Testing the non-200 error path

@isTest
public class CurrencyApiErrorMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest request) {
        HttpResponse response = new HttpResponse();
        response.setStatusCode(400);
        response.setBody('{"error": "Unrecognized currency code"}');
        return response;
    }
}

@isTest
static void convertThrowsOnBadCurrencyCode() {
    Test.setMock(HttpCalloutMock.class, new CurrencyApiErrorMock());

    try {
        new CurrencyConversionService().convert(100, 'XYZ');
        System.assert(false, 'Expected a CurrencyConversionException');
    } catch (CurrencyConversionException e) {
        System.assert(e.getMessage().contains('400'));
    }
}

This confirms Lesson 5's error-handling branch fires correctly on exactly the kind of realistic failure named back in Lesson 1's design — an unrecognized currency code.

Testing a CalloutException with a throwing mock

@isTest
public class CurrencyApiTimeoutMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest request) {
        throw new CalloutException('Read timed out');
    }
}

@isTest
static void convertHandlesNetworkFailure() {
    Test.setMock(HttpCalloutMock.class, new CurrencyApiTimeoutMock());

    try {
        new CurrencyConversionService().convert(100, 'USD');
        System.assert(false, 'Expected a CurrencyConversionException');
    } catch (CurrencyConversionException e) {
        System.assert(e.getMessage().contains('Could not reach'));
    }
}

A mock's respond() method can throw directly, simulating exactly the "no response received at all" scenario Lesson 5 distinguished from a non-200 response — confirming the try/catch around new Http().send(request) actually catches it and wraps it correctly.

Exercise

Write a mock returning a 500 status, and a test confirming convert() throws a CurrencyConversionException mentioning "500" for that response.

Show hint

Follow the same pattern as the 400 mock and test.

APEX

Test with HttpCalloutMock Quiz

1. How does a mock class simulate a genuine network failure (CalloutException), rather than an error response?

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

This lesson tests all three paths built across this module — success, a non-200 error, and a genuine callout failure — following Module 36's testing-integrations lesson directly.