afv-library/skills/platform-apex-test-run/assets/mock-callout-test.cls

354 lines
14 KiB
OpenEdge ABL

/**
* @description Test class for external API callouts using mock framework
* Demonstrates HttpCalloutMock and WebServiceMock patterns.
* @created {{Date}}
*/
@IsTest
private class {{ClassName}}CalloutTest {
//
// MOCK IMPLEMENTATIONS
//
/**
* @description Mock for successful HTTP response (200 OK)
*/
private class SuccessMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(200);
res.setStatus('OK');
res.setHeader('Content-Type', 'application/json');
res.setBody(JSON.serialize(new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>{
'id' => '12345',
'status' => 'completed',
'timestamp' => Datetime.now().format()
}
}));
return res;
}
}
/**
* @description Mock for error HTTP response (500 Internal Server Error)
*/
private class ErrorMock implements HttpCalloutMock {
private Integer statusCode;
private String errorMessage;
public ErrorMock(Integer statusCode, String errorMessage) {
this.statusCode = statusCode;
this.errorMessage = errorMessage;
}
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(this.statusCode);
res.setStatus('Error');
res.setHeader('Content-Type', 'application/json');
res.setBody(JSON.serialize(new Map<String, Object>{
'success' => false,
'error' => this.errorMessage
}));
return res;
}
}
/**
* @description Mock for timeout simulation
*/
private class TimeoutMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest req) {
// Simulate timeout by throwing CalloutException
throw new CalloutException('Read timed out');
}
}
/**
* @description Mock that validates request parameters
*/
private class ValidatingMock implements HttpCalloutMock {
private String expectedEndpoint;
private String expectedMethod;
public ValidatingMock(String endpoint, String method) {
this.expectedEndpoint = endpoint;
this.expectedMethod = method;
}
public HttpResponse respond(HttpRequest req) {
// Validate the request
System.assertEquals(expectedMethod, req.getMethod(),
'HTTP method should be ' + expectedMethod);
System.assert(req.getEndpoint().contains(expectedEndpoint),
'Endpoint should contain ' + expectedEndpoint);
HttpResponse res = new HttpResponse();
res.setStatusCode(200);
res.setBody('{"validated": true}');
return res;
}
}
//
// SUCCESS TESTS
//
/**
* @description Tests successful API call returns expected data
*/
@IsTest
static void testCallout_Success_ReturnsData() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new SuccessMock());
// WHEN
Test.startTest();
// TODO: Replace with your actual service call
// {{ClassName}}Response response = {{ClassName}}.callExternalAPI('test-param');
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/test');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
Test.stopTest();
// THEN
Assert.areEqual(200, res.getStatusCode(), 'Should return 200 OK');
Map<String, Object> responseBody = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
Assert.areEqual(true, responseBody.get('success'), 'Response should indicate success');
Assert.isNotNull(responseBody.get('data'), 'Response should contain data');
}
/**
* @description Tests POST request with body
*/
@IsTest
static void testCallout_PostWithBody_Success() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new SuccessMock());
Map<String, Object> requestBody = new Map<String, Object>{
'name' => 'Test Record',
'value' => 100
};
// WHEN
Test.startTest();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/create');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(requestBody));
HttpResponse res = new Http().send(req);
Test.stopTest();
// THEN
Assert.areEqual(200, res.getStatusCode(), 'POST should succeed');
}
//
// ERROR HANDLING TESTS
//
/**
* @description Tests handling of 400 Bad Request
*/
@IsTest
static void testCallout_BadRequest_HandlesGracefully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new ErrorMock(400, 'Invalid request parameters'));
// WHEN
Test.startTest();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/test');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
Test.stopTest();
// THEN
Assert.areEqual(400, res.getStatusCode(), 'Should return 400');
Map<String, Object> errorBody = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
Assert.areEqual(false, errorBody.get('success'), 'Should indicate failure');
Assert.isNotNull(errorBody.get('error'), 'Should contain error message');
}
/**
* @description Tests handling of 500 Internal Server Error
*/
@IsTest
static void testCallout_ServerError_HandlesGracefully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new ErrorMock(500, 'Internal server error'));
// WHEN
Test.startTest();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/test');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
Test.stopTest();
// THEN
Assert.areEqual(500, res.getStatusCode(), 'Should return 500');
}
/**
* @description Tests handling of timeout exception
*/
@IsTest
static void testCallout_Timeout_ThrowsException() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new TimeoutMock());
// WHEN/THEN
Test.startTest();
try {
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/slow');
req.setMethod('GET');
new Http().send(req);
Assert.fail('Expected CalloutException was not thrown');
} catch (CalloutException e) {
Assert.isTrue(e.getMessage().containsIgnoreCase('timeout'),
'Exception should mention timeout: ' + e.getMessage());
}
Test.stopTest();
}
/**
* @description Tests handling of 401 Unauthorized
*/
@IsTest
static void testCallout_Unauthorized_HandlesGracefully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new ErrorMock(401, 'Invalid or expired token'));
// WHEN
Test.startTest();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/protected');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
Test.stopTest();
// THEN
Assert.areEqual(401, res.getStatusCode(), 'Should return 401 Unauthorized');
}
//
// REQUEST VALIDATION TESTS
//
/**
* @description Verifies correct endpoint and method are used
*/
@IsTest
static void testCallout_ValidatesRequestParameters() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new ValidatingMock('/api/v1/resource', 'POST'));
// WHEN
Test.startTest();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/api/v1/resource');
req.setMethod('POST');
req.setBody('{"test": true}');
HttpResponse res = new Http().send(req);
Test.stopTest();
// THEN - Mock validates; test passes if no assertion failure
Assert.areEqual(200, res.getStatusCode(), 'Request should be valid');
}
//
// ASYNC CALLOUT TESTS (@future, Queueable)
//
/**
* @description Tests @future method with callout
* Note: Must use Test.startTest/stopTest to execute @future
*/
@IsTest
static void testFutureCallout_ExecutesSuccessfully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new SuccessMock());
// WHEN - Call @future method
Test.startTest();
// TODO: Replace with your actual @future method
// {{ClassName}}.makeAsyncCallout('test-param');
Test.stopTest(); // Forces @future to execute
// THEN - Verify results (may need to query for side effects)
// TODO: Add assertions based on what @future method does
}
/**
* @description Tests Queueable with callout
*/
@IsTest
static void testQueueableCallout_ExecutesSuccessfully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new SuccessMock());
// WHEN
Test.startTest();
// TODO: Replace with your actual Queueable
// System.enqueueJob(new {{ClassName}}Queueable('param'));
Test.stopTest(); // Forces Queueable to execute
// THEN - Verify results
// TODO: Add assertions
}
//
// MULTI-CALLOUT TESTS
//
/**
* @description Mock for multiple sequential callouts
*/
private class MultiCalloutMock implements HttpCalloutMock {
private Integer callCount = 0;
public HttpResponse respond(HttpRequest req) {
callCount++;
HttpResponse res = new HttpResponse();
res.setStatusCode(200);
res.setBody('{"callNumber": ' + callCount + '}');
return res;
}
}
/**
* @description Tests multiple callouts in sequence (limit: 100 per transaction)
*/
@IsTest
static void testMultipleCallouts_AllSucceed() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new MultiCalloutMock());
// WHEN - Make multiple callouts
Test.startTest();
List<HttpResponse> responses = new List<HttpResponse>();
for (Integer i = 0; i < 5; i++) {
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/call/' + i);
req.setMethod('GET');
responses.add(new Http().send(req));
}
Test.stopTest();
// THEN
Assert.areEqual(5, responses.size(), 'Should complete 5 callouts');
for (HttpResponse res : responses) {
Assert.areEqual(200, res.getStatusCode(), 'Each callout should succeed');
}
}
}