Test with Mocks
By the end of this lesson, you'll be able to:
- Test the success, decline, and error paths using HttpCalloutMock
- Test the retry logic specifically, confirming it stops at the attempt limit
- Apply Module 31 and Module 38's testing patterns to asynchronous, mocked code together
Prerequisites: "Secure Sensitive Data"
Testing the success path
@isTest
public class PaymentGatewaySuccessMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest request) {
HttpResponse response = new HttpResponse();
response.setStatusCode(200);
response.setBody('{"transactionId": "txn_123"}');
return response;
}
}
@isTest
private class ProcessPaymentJobTest {
@isTest
static void successfulPaymentMarksOrderPaid() {
Order__c order = new Order__c(Total__c = 500, Payment_Token__c = 'tok_abc');
insert order;
Test.setMock(HttpCalloutMock.class, new PaymentGatewaySuccessMock());
Test.startTest();
System.enqueueJob(new ProcessPaymentJob(order.Id, 500, 'tok_abc', 1));
Test.stopTest(); // forces the Queueable job to actually run before this line returns
Order__c updated = [SELECT Status__c, Payment_Reference__c FROM Order__c WHERE Id = :order.Id];
System.assertEquals('Paid', updated.Status__c);
System.assertEquals('txn_123', updated.Payment_Reference__c);
}
}
Test.stopTest() (Module 31) does double duty here — it forces the Queueable job to run synchronously to completion, meaning the assertion afterward can safely check the result of the async work, not just that it was enqueued.
Testing the decline path
@isTest
public class PaymentGatewayDeclineMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest request) {
HttpResponse response = new HttpResponse();
response.setStatusCode(402);
response.setBody('{"reason": "Insufficient funds"}');
return response;
}
}
@isTest
static void declinedPaymentMarksOrderDeclined() {
Order__c order = new Order__c(Total__c = 500, Payment_Token__c = 'tok_abc');
insert order;
Test.setMock(HttpCalloutMock.class, new PaymentGatewayDeclineMock());
Test.startTest();
System.enqueueJob(new ProcessPaymentJob(order.Id, 500, 'tok_abc', 1));
Test.stopTest();
Order__c updated = [SELECT Status__c, Decline_Reason__c FROM Order__c WHERE Id = :order.Id];
System.assertEquals('Payment Declined', updated.Status__c);
System.assertEquals('Insufficient funds', updated.Decline_Reason__c);
}
This confirms 402 gets routed correctly through Lesson 2's status-code branching all the way to the final Order__c status — the full chain, exercised end to end.
Testing that the retry limit is respected
@isTest
public class PaymentGatewayErrorMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest request) {
HttpResponse response = new HttpResponse();
response.setStatusCode(500);
return response;
}
}
@isTest
static void thirdFailedAttemptGivesUpWithPaymentError() {
Order__c order = new Order__c(Total__c = 500, Payment_Token__c = 'tok_abc');
insert order;
Test.setMock(HttpCalloutMock.class, new PaymentGatewayErrorMock());
Test.startTest();
System.enqueueJob(new ProcessPaymentJob(order.Id, 500, 'tok_abc', 3)); // already the 3rd attempt
Test.stopTest();
Order__c updated = [SELECT Status__c FROM Order__c WHERE Id = :order.Id];
System.assertEquals('Payment Error', updated.Status__c);
}
Starting at attemptNumber = 3 (the limit) directly, rather than simulating all three attempts, tests Lesson 4's boundary condition precisely — confirming the job gives up and records Payment Error instead of chaining a fourth attempt.
Exercise
Write a test confirming that an attempt with attemptNumber = 1 and a 500 error response results in the job chaining a retry (you can confirm this by checking a new AsyncApexJob exists after Test.stopTest, since chaining during tests only allows one level).
Show hint
Query AsyncApexJob after Test.stopTest() and check for a queued job.
Test with Mocks 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 every path built across this module — success, decline, error-with-retry, and the retry limit itself — combining Module 36's mocking with Module 31's async-testing habits.