Custom Headers
Add and test custom HTTP headers in your mocked responses.
Basic Header Usage
Add a single custom header:
@IsTest
static void testSingleHeader() {
new HttpMock()
.whenGetOn('/api/users')
.body('{"users": []}')
.header('X-Total-Count', '100')
.statusCodeOk()
.mock();
Test.startTest();
ApiResponse response = new UserService().getUsers();
Test.stopTest();
Assert.areEqual(100, response.totalCount);
}Multiple Headers
Chain multiple .header() calls:
@IsTest
static void testMultipleHeaders() {
new HttpMock()
.whenGetOn('/api/data')
.body('{"data": []}')
.header('X-Request-ID', 'abc-123')
.header('X-API-Version', 'v2')
.header('Cache-Control', 'no-cache')
.header('X-RateLimit-Remaining', '99')
.statusCodeOk()
.mock();
Test.startTest();
ApiResponse response = new ApiService().getData();
Test.stopTest();
Assert.areEqual('abc-123', response.requestId);
Assert.areEqual('v2', response.apiVersion);
}Pagination Headers
Use headers to communicate pagination metadata:
@IsTest
static void testPaginationHeaders() {
new HttpMock()
.whenGetOn('/api/users?page=2')
.body('{"users": [...]}')
.header('X-Total-Count', '1000')
.header('X-Page-Number', '2')
.header('X-Page-Size', '50')
.header('X-Total-Pages', '20')
.header('Link', '</api/users?page=3>; rel="next", </api/users?page=1>; rel="prev"')
.statusCodeOk()
.mock();
Test.startTest();
PaginatedResponse response = new UserService().getUsers(2);
Test.stopTest();
Assert.areEqual(1000, response.totalCount);
Assert.areEqual(2, response.currentPage);
Assert.areEqual(20, response.totalPages);
}Cache Headers
Test cache-related headers:
@IsTest
static void testCacheHeaders() {
new HttpMock()
.whenGetOn('/api/static-data')
.body('{"data": "cached content"}')
.header('Cache-Control', 'public, max-age=3600')
.header('ETag', '"abc123"')
.header('Last-Modified', 'Wed, 21 Oct 2023 07:28:00 GMT')
.header('Expires', 'Wed, 21 Oct 2023 08:28:00 GMT')
.statusCodeOk()
.mock();
Test.startTest();
CachedResponse response = new ApiService().getCachedData();
Test.stopTest();
Assert.isTrue(response.isCacheable);
Assert.areEqual(3600, response.maxAge);
}Rate Limiting Headers
Mock rate limit information:
@IsTest
static void testRateLimitHeaders() {
new HttpMock()
.whenGetOn('/api/limited')
.body('{"data": {}}')
.header('X-RateLimit-Limit', '100')
.header('X-RateLimit-Remaining', '95')
.header('X-RateLimit-Reset', '1640000000')
.statusCodeOk()
.mock();
Test.startTest();
ApiResponse response = new ApiService().callLimitedEndpoint();
Test.stopTest();
Assert.areEqual(100, response.rateLimit);
Assert.areEqual(95, response.rateLimitRemaining);
Assert.isFalse(response.rateLimitExceeded);
}File Download Headers
Test file download with Content-Disposition:
@IsTest
static void testFileDownload() {
Blob pdfContent = Blob.valueOf('PDF content here');
new HttpMock()
.whenGetOn('/api/reports/monthly.pdf')
.body(pdfContent)
.contentTypePdf()
.header('Content-Disposition', 'attachment; filename="monthly-report.pdf"')
.header('Content-Length', String.valueOf(pdfContent.size()))
.statusCodeOk()
.mock();
Test.startTest();
FileDownload download = new ReportService().downloadMonthlyReport();
Test.stopTest();
Assert.areEqual('monthly-report.pdf', download.filename);
Assert.isNotNull(download.content);
}CORS Headers
Mock CORS headers for cross-origin requests:
@IsTest
static void testCorsHeaders() {
new HttpMock()
.whenGetOn('/api/public/data')
.body('{"data": []}')
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
.header('Access-Control-Max-Age', '3600')
.statusCodeOk()
.mock();
Test.startTest();
ApiResponse response = new ApiService().getPublicData();
Test.stopTest();
Assert.isNotNull(response);
}Authentication Headers
Test authentication token headers:
@IsTest
static void testAuthHeaders() {
new HttpMock()
.whenPostOn('/api/login')
.body('{"userId": "123"}')
.header('X-Auth-Token', 'xyz789')
.header('Set-Cookie', 'session=abc123; HttpOnly; Secure; SameSite=Strict')
.statusCodeOk()
.mock();
Test.startTest();
AuthResponse response = new AuthService().login('user', 'pass');
Test.stopTest();
Assert.areEqual('xyz789', response.authToken);
Assert.isNotNull(response.sessionCookie);
}Custom Business Headers
Use headers for custom business logic:
@IsTest
static void testBusinessHeaders() {
new HttpMock()
.whenGetOn('/api/orders')
.body('{"orders": []}')
.header('X-Tenant-ID', 'tenant-123')
.header('X-Feature-Flags', 'feature1,feature2')
.header('X-Correlation-ID', 'corr-456')
.header('X-Server-Time', '2023-10-21T12:00:00Z')
.statusCodeOk()
.mock();
Test.startTest();
ApiResponse response = new OrderService().getOrders();
Test.stopTest();
Assert.areEqual('tenant-123', response.tenantId);
Assert.isTrue(response.hasFeature('feature1'));
}Override Content-Type
You can override the default Content-Type using .header():
@IsTest
static void testCustomContentType() {
new HttpMock()
.whenGetOn('/api/custom')
.body('{"data": {}}')
.header('Content-Type', 'application/vnd.api+json; charset=utf-8')
.statusCodeOk()
.mock();
Test.startTest();
ApiResponse response = new ApiService().getCustomFormat();
Test.stopTest();
Assert.isNotNull(response);
}Debugging Headers
Add debugging information via headers:
@IsTest
static void testDebugHeaders() {
new HttpMock()
.whenGetOn('/api/debug')
.body('{"data": {}}')
.header('X-Request-ID', 'req-123-456')
.header('X-Server-ID', 'server-42')
.header('X-Request-Duration', '127ms')
.header('X-Cache-Status', 'MISS')
.statusCodeOk()
.mock();
Test.startTest();
ApiResponse response = new ApiService().callWithDebug();
Test.stopTest();
Assert.areEqual('req-123-456', response.requestId);
Assert.areEqual(127, response.durationMs);
}Location Header
Test redirect and created resource headers:
@IsTest
static void testLocationHeader() {
new HttpMock()
.whenPostOn('/api/users')
.body('{"id": "123"}')
.header('Location', '/api/users/123')
.statusCodeCreated()
.mock();
Test.startTest();
CreateResponse response = new UserService().createUser('John Doe');
Test.stopTest();
Assert.areEqual('123', response.id);
Assert.areEqual('/api/users/123', response.location);
}Deprecation Headers
Communicate API deprecation:
@IsTest
static void testDeprecationHeaders() {
new HttpMock()
.whenGetOn('/api/v1/old-endpoint')
.body('{"data": {}}')
.header('Deprecation', 'true')
.header('Sunset', 'Sat, 31 Dec 2024 23:59:59 GMT')
.header('Link', '</api/v2/new-endpoint>; rel="alternate"')
.statusCodeOk()
.mock();
Test.startTest();
ApiResponse response = new ApiService().callOldEndpoint();
Test.stopTest();
Assert.isTrue(response.isDeprecated);
Assert.isNotNull(response.sunsetDate);
}Best Practices
Use Standard Headers - Prefer standard HTTP headers when possible
Namespace Custom Headers - Use
X-prefix for custom headers (though this is deprecated in RFC 6648, it's still common)Test Header Parsing - Verify your code correctly reads and uses headers
Match Real APIs - Include the same headers the actual API returns
Document Header Purpose - Comment what each custom header represents
