Advanced Testing & Mocking
26.1 Mocking HTTP callouts
Tests cannot make real callouts. Implement HttpCalloutMock:
Alternatives: StaticResourceCalloutMock (response from a static resource), MultiStaticResourceCalloutMock (different resources per endpoint), and WebServiceMock for SOAP stubs generated by WSDL2Apex (Test.setMock(WebServiceMock.class, new MySoapMock())).
If a test performs DML and then a callout, you'll get *"You have uncommitted work pending"*. Do the DML before Test.startTest() and the callout between startTest and stopTest.
@isTest
public class WeatherCalloutMock implements HttpCalloutMock {
private Integer statusCode;
private String body;
public WeatherCalloutMock(Integer statusCode, String body) {
this.statusCode = statusCode; this.body = body;
}
public HttpResponse respond(HttpRequest req) {
Assert.areEqual('GET', req.getMethod());
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setStatusCode(statusCode);
res.setBody(body);
return res;
}
}
@isTest
private class WeatherServiceTest {
@isTest
static void parsesTemperature() {
Test.setMock(HttpCalloutMock.class, new WeatherCalloutMock(200, '{"tempC": 21.5}'));
Test.startTest();
Decimal t = WeatherService.currentTemp('Pune');
Test.stopTest();
Assert.areEqual(21.5, t);
}
@isTest
static void handlesServerError() {
Test.setMock(HttpCalloutMock.class, new WeatherCalloutMock(500, 'oops'));
try {
WeatherService.currentTemp('Pune');
Assert.fail('Expected exception');
} catch (WeatherService.WeatherException e) {
Asse
…26.2 The Stub API
System.StubProvider + Test.createStub() create mock implementations of non-static methods on your classes, without real database work:
Stub API limitations: the stub can only intercept non-static, non-private instance methods; you cannot stub static methods, constructors, private methods, properties' get/set accessors, system classes, or classes from another namespace; and the code under test must receive the stub (via constructor or setter injection, §36) instead of instantiating the real class itself.
Frameworks built on the Stub API: ApexMocks (fflib, Mockito-style when().thenReturn() and verify()), Amoss, UniversalMocker.
public virtual class AccountSelector { // must be stubbable: not final, visible
public virtual List<Account> selectByIndustry(String industry) {
return [SELECT Id, Name FROM Account WHERE Industry = :industry];
}
}
@isTest
public class AccountSelectorStub implements System.StubProvider {
public Object handleMethodCall(Object stubbedObject, String stubbedMethodName,
Type returnType, List<Type> listOfParamTypes,
List<String> listOfParamNames, List<Object> listOfArgs) {
if (stubbedMethodName == 'selectByIndustry') {
return new List<Account>{
new Account(Id = fakeId(), Name = 'Mocked ' + listOfArgs[0])
};
}
return null;
}
private static Id fakeId() {
return Id.valueOf('001' + '0'.repeat(11) + '1'); // fake 15-char Account ID (never inserted)
}
}
@isTest
static void serviceUsesSelector() {
AccountSelector stub = (AccountSelector) Test.createStub(AccountSelector.class, new AccountSelectorStub());
AccountService svc = new AccountService(stub); // constructor inje
…26.3 Testing async code
@future, Queueable Call inside Test.startTest()/stopTest(); runs at stopTest
Only the first job runs in tests; guard chaining with Test.isRunningTest() or test each job separately
Database.executeBatch inside start/stop; only one execute chunk runs in tests — keep test data ≤ scope size
System.schedule inside start/stop; execute runs at stopTest (but anything it enqueues does not)
Test.getEventBus().deliver() or stopTest Test the finalizer class directly by implementing a fake FinalizerContext, or rely on stopTest
26.4 Test quality checklist
Every test has at least one meaningful assertion with a message.
Positive, negative, bulk (≥ 200 records), and permission (runAs) cases.
No SeeAllData=true; no hard-coded IDs; no dependency on org configuration that may change.
Data created via a factory; @testSetup for shared fixtures.
Unit tests use mocks/stubs for selectors and callouts; a few integration tests hit the database.
Tests run fast and in parallel.