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.
Test with HttpCalloutMock Quiz
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.