diff --git a/README.md b/README.md
index 2de5f09..fce69cb 100644
--- a/README.md
+++ b/README.md
@@ -8,13 +8,14 @@
HTTP Mock Lib
-
+
[](https://github.com/beyond-the-cloud-dev/http-mock-lib/actions/workflows/ci.yml)
-[](https://codecov.io/gh/beyond-the-cloud-dev/http-mock-lib)
+# Getting Started
+
HTTP Mock inspired by Robert Sösemann’s [Apex Http Mock](https://github.com/rsoesemann/apex-httpmock).
HTTP Mock Lib is part of [Apex Fluently](https://apexfluently.beyondthecloud.dev/), a suite of production-ready Salesforce libraries by Beyond the Cloud.
@@ -113,95 +114,17 @@ public interface HttpMockLib {
}
```
-## Features
-
-### Mock different HTTP methods
-
-Mock different HTTP methods in the same test method.
-
-```java
-new HttpMock()
- .whenGetOn('/api/v1/authorize').body('{ "token": "aZ3Xb7Qk" }').statusCodeOk()
- .whenPostOn('/api/v1/create').body('{ "success": true, "message": null }').statusCodeOk()
- .mock();
-```
-
-Supported methods:
-- GET
-- POST
-- PUT
-- PATCH
-- DELETE
-- TRACE
-- HEAD
+## Documentation
-### Return different body
+Visit the [documentation](https://httpmock.beyondthecloud.dev/) to view the full documentation.
-```java
-new HttpMock()
- .whenGetOn('/api/v1').body(new Map{ 'token' => 'aZ3Xb7Qk' }).statusCodeOk()
- .mock();
-```
-
-Mock HTTP response body by using the following methods:
+## Contributors
-```java
-HttpMock body(Object body);
-HttpMock body(String body);
-HttpMock body(Blob body);
-```
+
+
+
-### Use built-in Content Types
+## License notes:
-Use different content types. By default, the content type is set to `application/json`.
-
-```java
-HttpMock contentTypePlainText(); // text/plain
-HttpMock contentTypeHtml(); // text/html
-HttpMock contentTypeCsv(); // text/csv
-HttpMock contentTypeJson(); // application/json
-HttpMock contentTypeXml(); // application/xml
-HttpMock contentTypePdf(); // application/pdf
-HttpMock contentTypeFormUrlencoded(); // application/x-www-form-urlencoded
-
-HttpMock contentType(String contentType);
-```
-
-Use `contentType(String contentType)` to set your own content type.
-
-### Use built-in Status Codes
-
-Use different status codes. By default, the status code is set to 200 (OK).
-
-Available status codes:
-
-```java
-HttpMock statusCodeOk(); // 200
-HttpMock statusCodeCreated(); // 201
-HttpMock statusCodeAccepted(); // 202
-HttpMock statusCodeNoContent(); // 204
-HttpMock statusCodeBadRequest(); // 400
-HttpMock statusCodeUnauthorized(); // 401
-HttpMock statusCodeForbidden(); // 403
-HttpMock statusCodeNotFound(); // 404
-HttpMock statusCodeMethodNotAllowed(); // 405
-HttpMock statusCodeInternalServerError(); // 500
-HttpMock statusCodeNotImplemented(); // 501
-HttpMock statusCodeBadGateway(); // 502
-HttpMock statusCodeServiceUnavailable(); // 503
-HttpMock statusCodeGatewayTimeout(); // 504
-
-HttpMock statusCode(Integer statusCode);
-```
-
-Use `statusCode(Integer statusCode)` to set your own status code.
-
-### Set custom headers
-
-Set response headers using the `header(String key, String value)` method.
-
-```java
-new HttpMock()
- .whenGetOn('/api/v1').body('{ "token": "aZ3Xb7Qk" }').header('Cache-Control', 'no-cache')
- .mock();
-```
+- For proper license management each repository should contain LICENSE file similar to this one.
+- each original class should contain copyright mark: © Copyright 2025, Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
diff --git a/force-app/main/default/classes/HttpMock.cls b/force-app/main/default/classes/HttpMock.cls
index 21fa105..274a0f1 100644
--- a/force-app/main/default/classes/HttpMock.cls
+++ b/force-app/main/default/classes/HttpMock.cls
@@ -4,103 +4,130 @@
*
* PMD False Positives:
* - ExcessivePublicCount: It is a library class and exposes all necessary methods to construct a http mock.
+ * - FieldDeclarationsShouldBeAtStart: It is a library class and important methods are on the top.
+ * - CognitiveComplexity: It is a library class and the code is complex but it is necessary to keep it this way.
+ * - CyclomaticComplexity: It is a library class and the code is complex but it is necessary to keep it this way.
**/
+@SuppressWarnings('PMD.ExcessivePublicCount,PMD.FieldDeclarationsShouldBeAtStart,PMD.CognitiveComplexity,PMD.CyclomaticComplexity')
@IsTest
-@SuppressWarnings('PMD.ExcessivePublicCount')
-public class HttpMock implements HttpMockLib, HttpCalloutMock {
+global class HttpMock implements HttpStubbing, HttpCalloutMock {
/*
- new HttpMock()
- .whenGetOn('/api/v1/authorize').body('{ "token": "aZ3Xb7Qk" }').contentTypeJson().statusCodeOk()
- .whenPostOn('/api/v1/create').body('{ "success": true, "message": null }').contentTypeJson().statusCodeOk()
+ new HttpMock
+ .whenGetOn('/api/v1/authorize')
+ .body('{ "token": "aZ3Xb7Qk" }')
+ .contentTypeJson()
+ .statusCodeOk()
+ .whenPostOn('/api/v1/create')
+ .body('{ "success": true, "message": null }')
+ .contentTypeJson()
+ .statusCodeOk()
.mock();
+
+ Test.startTest();
+ // ...
+ Test.stopTest();
+
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1/authorize').get(), 'One GET request should be made');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1/create').post(), 'One POST request should be made');
*/
- public interface HttpMockLib {
- HttpMock whenGetOn(String endpointToMock);
- HttpMock whenPostOn(String endpointToMock);
- HttpMock whenPutOn(String endpointToMock);
- HttpMock whenPatchOn(String endpointToMock);
- HttpMock whenDeleteOn(String endpointToMock);
- HttpMock whenTraceOn(String endpointToMock);
- HttpMock whenHeadOn(String endpointToMock);
+ global interface HttpStubbing {
+ HttpStubbing whenGetOn(String endpointToMock);
+ HttpStubbing whenPostOn(String endpointToMock);
+ HttpStubbing whenPutOn(String endpointToMock);
+ HttpStubbing whenPatchOn(String endpointToMock);
+ HttpStubbing whenDeleteOn(String endpointToMock);
+ HttpStubbing whenTraceOn(String endpointToMock);
+ HttpStubbing whenHeadOn(String endpointToMock);
// Body
- HttpMock body(Object body);
- HttpMock body(String body);
- HttpMock body(Blob body);
+ HttpStubbing body(Object body);
+ HttpStubbing body(String body);
+ HttpStubbing body(Blob body);
+ // Static Resource
+ HttpStubbing staticResource(String staticResourceName);
// Content-Type
- HttpMock contentTypePlainText(); // text/plain
- HttpMock contentTypeHtml(); // text/html
- HttpMock contentTypeCsv(); // text/csv
- HttpMock contentTypeJson(); // application/json
- HttpMock contentTypeXml(); // application/xml
- HttpMock contentTypePdf(); // application/pdf
- HttpMock contentTypeFormUrlencoded(); // application/x-www-form-urlencoded
- HttpMock contentType(String contentType);
+ HttpStubbing contentTypePlainText(); // text/plain
+ HttpStubbing contentTypeHtml(); // text/html
+ HttpStubbing contentTypeCsv(); // text/csv
+ HttpStubbing contentTypeJson(); // application/json
+ HttpStubbing contentTypeXml(); // application/xml
+ HttpStubbing contentTypePdf(); // application/pdf
+ HttpStubbing contentTypeFormUrlencoded(); // application/x-www-form-urlencoded
+ HttpStubbing contentType(String contentType);
// Status Code
- HttpMock statusCodeOk(); // 200
- HttpMock statusCodeCreated(); // 201
- HttpMock statusCodeAccepted(); // 202
- HttpMock statusCodeNoContent(); // 204
- HttpMock statusCodeBadRequest(); // 400
- HttpMock statusCodeUnauthorized(); // 401
- HttpMock statusCodeForbidden(); // 403
- HttpMock statusCodeNotFound(); // 404
- HttpMock statusCodeMethodNotAllowed(); // 405
- HttpMock statusCodeInternalServerError(); // 500
- HttpMock statusCodeNotImplemented(); // 501
- HttpMock statusCodeBadGateway(); // 502
- HttpMock statusCodeServiceUnavailable(); // 503
- HttpMock statusCodeGatewayTimeout(); // 504
- HttpMock statusCode(Integer statusCode);
+ HttpStubbing statusCodeOk(); // 200
+ HttpStubbing statusCodeCreated(); // 201
+ HttpStubbing statusCodeAccepted(); // 202
+ HttpStubbing statusCodeNoContent(); // 204
+ HttpStubbing statusCodeBadRequest(); // 400
+ HttpStubbing statusCodeUnauthorized(); // 401
+ HttpStubbing statusCodeForbidden(); // 403
+ HttpStubbing statusCodeNotFound(); // 404
+ HttpStubbing statusCodeMethodNotAllowed(); // 405
+ HttpStubbing statusCodeInternalServerError(); // 500
+ HttpStubbing statusCodeNotImplemented(); // 501
+ HttpStubbing statusCodeBadGateway(); // 502
+ HttpStubbing statusCodeServiceUnavailable(); // 503
+ HttpStubbing statusCodeGatewayTimeout(); // 504
+ HttpStubbing statusCode(Integer statusCode);
// Headers
- HttpMock header(String key, String value);
+ HttpStubbing header(String key, String value);
// Mock
void mock();
}
- public static Integer getRequestCount(String httpMethod, String endpoint) {
- if (!requestCountByMethodAndEndpoint.containsKey(httpMethod)) {
- return 0;
- }
+ // Request Counts for assertions
+ global static Requests requestsTo(String endpoint) {
+ return HttpMock.requestsByEndpoint.get(endpoint) ?? new HttpMockRequests();
+ }
+
+ global interface Requests {
+ Integer all();
- return requestCountByMethodAndEndpoint.get(httpMethod).get(endpoint) ?? 0;
+ Integer get();
+ Integer post();
+ Integer put();
+ Integer patch();
+ Integer deletex(); // delete is a reserved keyword
+ Integer trace();
+ Integer head();
}
// Implementation
private static Map>> mocks = new Map>>();
- private static Map> requestCountByMethodAndEndpoint = new Map>();
+ private static Map requestsByEndpoint = new Map();
private HttpResponse workingHttpResponse = null;
-
- public HttpMock whenGetOn(String endpointToMock) {
+
+ public HttpStubbing whenGetOn(String endpointToMock) {
return this.add('GET', endpointToMock);
}
- public HttpMock whenPostOn(String endpointToMock) {
+ public HttpStubbing whenPostOn(String endpointToMock) {
return this.add('POST', endpointToMock);
}
- public HttpMock whenPutOn(String endpointToMock) {
+ public HttpStubbing whenPutOn(String endpointToMock) {
return this.add('PUT', endpointToMock);
}
- public HttpMock whenPatchOn(String endpointToMock) {
+ public HttpStubbing whenPatchOn(String endpointToMock) {
return this.add('PATCH', endpointToMock);
}
- public HttpMock whenDeleteOn(String endpointToMock) {
+ public HttpStubbing whenDeleteOn(String endpointToMock) {
return this.add('DELETE', endpointToMock);
}
- public HttpMock whenTraceOn(String endpointToMock) {
+ public HttpStubbing whenTraceOn(String endpointToMock) {
return this.add('TRACE', endpointToMock);
}
- public HttpMock whenHeadOn(String endpointToMock) {
+ public HttpStubbing whenHeadOn(String endpointToMock) {
return this.add('HEAD', endpointToMock);
}
- private HttpMock add(String httpMethod, String endpointToMock) {
+ private HttpStubbing add(String httpMethod, String endpointToMock) {
this.initWhenEmpty(httpMethod, endpointToMock);
this.workingHttpResponse = new HttpResponse();
@@ -124,114 +151,126 @@ public class HttpMock implements HttpMockLib, HttpCalloutMock {
}
}
- public HttpMock body(Object body) {
+ public HttpStubbing body(Object body) {
return this.body(JSON.serialize(body));
}
- public HttpMock body(String body) {
+ public HttpStubbing body(String body) {
this.workingHttpResponse.setBody(body);
return this;
}
- public HttpMock body(Blob body) {
+ public HttpStubbing body(Blob body) {
this.workingHttpResponse.setBodyAsBlob(body);
return this;
}
- public HttpMock contentTypePlainText() {
+ public HttpStubbing contentTypePlainText() {
return this.contentType('text/plain');
}
- public HttpMock contentTypeHtml() {
+ public HttpStubbing contentTypeHtml() {
return this.contentType('text/html');
}
- public HttpMock contentTypeCsv() {
+ public HttpStubbing contentTypeCsv() {
return this.contentType('text/csv');
}
- public HttpMock contentTypeJson() {
+ public HttpStubbing contentTypeJson() {
return this.contentType('application/json');
}
- public HttpMock contentTypePdf() {
+ public HttpStubbing contentTypePdf() {
return this.contentType('application/pdf');
}
- public HttpMock contentTypeXml() {
+ public HttpStubbing contentTypeXml() {
return this.contentType('application/xml');
}
- public HttpMock contentTypeFormUrlencoded() {
+ public HttpStubbing contentTypeFormUrlencoded() {
return this.contentType('application/x-www-form-urlencoded');
}
- public HttpMock contentType(String contentType) {
+ public HttpStubbing contentType(String contentType) {
return this.header('Content-Type', contentType);
}
- public HttpMock header(String key, String value) {
+ public HttpStubbing header(String key, String value) {
this.workingHttpResponse.setHeader(key, value);
return this;
}
- public HttpMock statusCodeOk() {
+ public HttpStubbing staticResource(String staticResourceName) {
+ StaticResource staticResource = null;
+
+ try {
+ staticResource = [SELECT Body FROM StaticResource WHERE Name = :staticResourceName LIMIT 1];
+ } catch (QueryException queryEx) {
+ throw new StaticResourceNotFoundException('Static Resource "' + staticResourceName + '" not found.');
+ }
+
+ return this.body(staticResource.Body);
+ }
+
+ public HttpStubbing statusCodeOk() {
return this.statusCode(200);
}
- public HttpMock statusCodeCreated() {
+ public HttpStubbing statusCodeCreated() {
return this.statusCode(201);
}
- public HttpMock statusCodeAccepted() {
+ public HttpStubbing statusCodeAccepted() {
return this.statusCode(202);
}
- public HttpMock statusCodeNoContent() {
+ public HttpStubbing statusCodeNoContent() {
return this.statusCode(204);
}
- public HttpMock statusCodeBadRequest() {
+ public HttpStubbing statusCodeBadRequest() {
return this.statusCode(400);
}
- public HttpMock statusCodeUnauthorized() {
+ public HttpStubbing statusCodeUnauthorized() {
return this.statusCode(401);
}
- public HttpMock statusCodeForbidden() {
+ public HttpStubbing statusCodeForbidden() {
return this.statusCode(403);
}
- public HttpMock statusCodeNotFound() {
+ public HttpStubbing statusCodeNotFound() {
return this.statusCode(404);
}
- public HttpMock statusCodeMethodNotAllowed() {
+ public HttpStubbing statusCodeMethodNotAllowed() {
return this.statusCode(405);
}
- public HttpMock statusCodeInternalServerError() {
+ public HttpStubbing statusCodeInternalServerError() {
return this.statusCode(500);
}
- public HttpMock statusCodeNotImplemented() {
+ public HttpStubbing statusCodeNotImplemented() {
return this.statusCode(501);
}
- public HttpMock statusCodeBadGateway() {
+ public HttpStubbing statusCodeBadGateway() {
return this.statusCode(502);
}
- public HttpMock statusCodeServiceUnavailable() {
+ public HttpStubbing statusCodeServiceUnavailable() {
return this.statusCode(503);
}
- public HttpMock statusCodeGatewayTimeout() {
+ public HttpStubbing statusCodeGatewayTimeout() {
return this.statusCode(504);
}
- public HttpMock statusCode(Integer statusCode) {
+ public HttpStubbing statusCode(Integer statusCode) {
this.workingHttpResponse.setStatusCode(statusCode);
return this;
}
@@ -241,7 +280,7 @@ public class HttpMock implements HttpMockLib, HttpCalloutMock {
}
public HttpResponse respond(HttpRequest request) {
- String closestMatchingMockedEndpoint = this.findClosestMatchingMockedEndpoint(request);
+ String closestMatchingMockedEndpoint = this.findClosestMatchingMockedEndpointBasedOnRequest(request);
String requestEndpoint = request.getEndpoint();
@@ -257,7 +296,7 @@ public class HttpMock implements HttpMockLib, HttpCalloutMock {
throw new HttpEndpointNotMockedException('HTTP Endpoint ' + requestMethod + ' ' + requestEndpoint + ' hasn\'t been mocked.');
}
- this.incrementRequestCount(requestMethod, closestMatchingMockedEndpoint);
+ this.registerRequest(requestMethod, requestEndpoint);
if (mockedHttpResponses.size() > 1) {
return mockedHttpResponses.remove(0);
@@ -266,14 +305,22 @@ public class HttpMock implements HttpMockLib, HttpCalloutMock {
return mockedHttpResponses.get(0);
}
- private String findClosestMatchingMockedEndpoint(HttpRequest httpRequest) {
+ private void registerRequest(String httpMethod, String endpoint) {
+ if (!requestsByEndpoint.containsKey(endpoint)) {
+ requestsByEndpoint.put(endpoint, new HttpMockRequests());
+ }
+
+ requestsByEndpoint.get(endpoint).incrementRequestCount(httpMethod);
+ }
+
+ private String findClosestMatchingMockedEndpointBasedOnRequest(HttpRequest httpRequest) {
String httpRequestMethod = httpRequest.getMethod();
+ String httpRequestEndpoint = httpRequest.getEndpoint();
if (!mocks.containsKey(httpRequestMethod)) {
throw new HttpMethodNotMockedException('HTTP Method ' + httpRequestMethod + ' hasn\'t been mocked.');
}
- String httpRequestEndpoint = httpRequest.getEndpoint();
return this.findClosestMatchingMockedEndpoint(httpRequestEndpoint, mocks.get(httpRequestMethod).keySet());
}
@@ -291,15 +338,57 @@ public class HttpMock implements HttpMockLib, HttpCalloutMock {
return closestMatchingMockedEndpoint;
}
- private void incrementRequestCount(String httpMethod, String endpoint) {
- if (!requestCountByMethodAndEndpoint.containsKey(httpMethod)) {
- requestCountByMethodAndEndpoint.put(httpMethod, new Map());
+ private class HttpMockRequests implements Requests {
+ private Map requestCountByMethod = new Map();
+
+ public void incrementRequestCount(String httpMethod) {
+ if (!this.requestCountByMethod.containsKey(httpMethod)) {
+ this.requestCountByMethod.put(httpMethod, 0);
+ }
+
+ this.requestCountByMethod.put(httpMethod, this.requestCountByMethod.get(httpMethod) + 1);
+ }
+
+ public Integer all() {
+ Integer total = 0;
+
+ for (Integer count : this.requestCountByMethod.values()) {
+ total += count;
+ }
+
+ return total;
+ }
+
+ public Integer get() {
+ return this.requestCountByMethod.get('GET') ?? 0;
}
- Integer currentCount = requestCountByMethodAndEndpoint.get(httpMethod).get(endpoint) ?? 0;
- requestCountByMethodAndEndpoint.get(httpMethod).put(endpoint, currentCount + 1);
+ public Integer post() {
+ return this.requestCountByMethod.get('POST') ?? 0;
+ }
+
+ public Integer put() {
+ return this.requestCountByMethod.get('PUT') ?? 0;
+ }
+
+ public Integer patch() {
+ return this.requestCountByMethod.get('PATCH') ?? 0;
+ }
+
+ public Integer deletex() {
+ return this.requestCountByMethod.get('DELETE') ?? 0;
+ }
+
+ public Integer trace() {
+ return this.requestCountByMethod.get('TRACE') ?? 0;
+ }
+
+ public Integer head() {
+ return this.requestCountByMethod.get('HEAD') ?? 0;
+ }
}
public class HttpMethodNotMockedException extends Exception {}
public class HttpEndpointNotMockedException extends Exception {}
+ public class StaticResourceNotFoundException extends Exception {}
}
diff --git a/force-app/main/default/classes/HttpMock.cls-meta.xml b/force-app/main/default/classes/HttpMock.cls-meta.xml
index 53b88f8..594adb8 100644
--- a/force-app/main/default/classes/HttpMock.cls-meta.xml
+++ b/force-app/main/default/classes/HttpMock.cls-meta.xml
@@ -1,5 +1,5 @@
- 64.0
+ 65.0
Active
\ No newline at end of file
diff --git a/force-app/main/default/classes/HttpMockTest.cls b/force-app/main/default/classes/HttpMockTest.cls
index fec9c29..5111d5f 100644
--- a/force-app/main/default/classes/HttpMockTest.cls
+++ b/force-app/main/default/classes/HttpMockTest.cls
@@ -1,8 +1,13 @@
/**
* Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
* Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/http-mock-lib/blob/main/LICENSE)
+ *
+ * PMD False Positives:
+ * - CognitiveComplexity: It is a test class and the code is complex but it is necessary to keep it this way.
+ * - CyclomaticComplexity: It is a test class and the code is complex but it is necessary to keep it this way.
**/
-@IsTest
+@SuppressWarnings('PMD.ApexUnitTestClassShouldHaveRunAs,PMD.CognitiveComplexity,PMD.CyclomaticComplexity')
+@IsTest(IsParallel=true)
private class HttpMockTest {
@IsTest
static void get() {
@@ -238,6 +243,33 @@ private class HttpMockTest {
Assert.areEqual('text/html', response.getHeader('Content-Type'), 'Content type should be text/html');
}
+ class SampleResponseBody {
+ public String name;
+ public Integer order;
+ public Boolean active;
+ public List additionalDetails;
+ }
+
+ @IsTest
+ static void staticResourceNotFound() {
+ String staticResourceName = 'aResourceThatDoesNotExist12345';
+
+ Test.startTest();
+ Exception caughtEx;
+
+ try {
+ new HttpMock()
+ .whenGetOn('/api/v1').staticResource(staticResourceName)
+ .mock();
+ } catch (Exception ex) {
+ caughtEx = ex;
+ }
+ Test.stopTest();
+
+ Assert.isNotNull(caughtEx, 'Exception should be thrown');
+ Assert.areEqual('Static Resource "' + staticResourceName + '" not found.', caughtEx.getMessage(), 'Message should be Static Resource "' + staticResourceName + '" not found.');
+ }
+
@IsTest
static void statusCodeOk() {
new HttpMock()
@@ -526,7 +558,55 @@ private class HttpMockTest {
}
@IsTest
- static void getRequestCount() {
+ static void getRequestsToWithMultipleEndpoints() {
+ new HttpMock()
+ .whenGetOn('/api/v1').statusCodeOk()
+ .whenGetOn('/api/v2').statusCodeOk()
+ .mock();
+
+ Test.startTest();
+ new TestApi().makeCallout('GET', '/api/v1');
+ new TestApi().makeCallout('GET', '/api/v2');
+ Test.stopTest();
+
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').get(), 'Request count should be 1');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v2').get(), 'Request count should be 1');
+ }
+
+ @IsTest
+ static void allRequestsTo() {
+ new HttpMock()
+ .whenGetOn('/api/v1').statusCodeOk()
+ .whenPostOn('/api/v1').statusCodeOk()
+ .whenPutOn('/api/v1').statusCodeOk()
+ .whenPatchOn('/api/v1').statusCodeOk()
+ .whenDeleteOn('/api/v1').statusCodeOk()
+ .whenTraceOn('/api/v1').statusCodeOk()
+ .whenHeadOn('/api/v1').statusCodeOk()
+ .mock();
+
+ Test.startTest();
+ new TestApi().makeCallout('GET', '/api/v1');
+ new TestApi().makeCallout('POST', '/api/v1');
+ new TestApi().makeCallout('PUT', '/api/v1');
+ new TestApi().makeCallout('PATCH', '/api/v1');
+ new TestApi().makeCallout('DELETE', '/api/v1');
+ new TestApi().makeCallout('TRACE', '/api/v1');
+ new TestApi().makeCallout('HEAD', '/api/v1');
+ Test.stopTest();
+
+ Assert.areEqual(7, HttpMock.requestsTo('/api/v1').all(), 'Request count should be 7');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').get(), 'Request count should be 1');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').post(), 'Request count should be 1');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').put(), 'Request count should be 1');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').patch(), 'Request count should be 1');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').deletex(), 'Request count should be 1');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').trace(), 'Request count should be 1');
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').head(), 'Request count should be 1');
+ }
+
+ @IsTest
+ static void getRequestsTo() {
new HttpMock().whenGetOn('/api/v1').statusCodeOk().mock();
Test.startTest();
@@ -534,7 +614,73 @@ private class HttpMockTest {
new TestApi().makeCallout('GET', '/api/v1');
Test.stopTest();
- Assert.areEqual(2, HttpMock.getRequestCount('GET', '/api/v1'), 'Request count should be 2');
+ Assert.areEqual(2, HttpMock.requestsTo('/api/v1').get(), 'Request count should be 2');
+ }
+
+ @IsTest
+ static void postRequestsTo() {
+ new HttpMock().whenPostOn('/api/v1').statusCodeOk().mock();
+
+ Test.startTest();
+ new TestApi().makeCallout('POST', '/api/v1');
+ Test.stopTest();
+
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').post(), 'Request count should be 1');
+ }
+
+ @IsTest
+ static void putRequestsTo() {
+ new HttpMock().whenPutOn('/api/v1').statusCodeOk().mock();
+
+ Test.startTest();
+ new TestApi().makeCallout('PUT', '/api/v1');
+ Test.stopTest();
+
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').put(), 'Request count should be 1');
+ }
+
+ @IsTest
+ static void patchRequestsTo() {
+ new HttpMock().whenPatchOn('/api/v1').statusCodeOk().mock();
+
+ Test.startTest();
+ new TestApi().makeCallout('PATCH', '/api/v1');
+ Test.stopTest();
+
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').patch(), 'Request count should be 1');
+ }
+
+ @IsTest
+ static void deleteRequestsTo() {
+ new HttpMock().whenDeleteOn('/api/v1').statusCodeOk().mock();
+
+ Test.startTest();
+ new TestApi().makeCallout('DELETE', '/api/v1');
+ Test.stopTest();
+
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').deletex(), 'Request count should be 1');
+ }
+
+ @IsTest
+ static void traceRequestsTo() {
+ new HttpMock().whenTraceOn('/api/v1').statusCodeOk().mock();
+
+ Test.startTest();
+ new TestApi().makeCallout('TRACE', '/api/v1');
+ Test.stopTest();
+
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').trace(), 'Request count should be 1');
+ }
+
+ @IsTest
+ static void headRequestsTo() {
+ new HttpMock().whenHeadOn('/api/v1').statusCodeOk().mock();
+
+ Test.startTest();
+ new TestApi().makeCallout('HEAD', '/api/v1');
+ Test.stopTest();
+
+ Assert.areEqual(1, HttpMock.requestsTo('/api/v1').head(), 'Request count should be 1');
}
@IsTest
diff --git a/force-app/main/default/classes/HttpMockTest.cls-meta.xml b/force-app/main/default/classes/HttpMockTest.cls-meta.xml
index 53b88f8..594adb8 100644
--- a/force-app/main/default/classes/HttpMockTest.cls-meta.xml
+++ b/force-app/main/default/classes/HttpMockTest.cls-meta.xml
@@ -1,5 +1,5 @@
- 64.0
+ 65.0
Active
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 88b946d..1d37c84 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "salesforce-repo",
+ "name": "http-mock-lib",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "salesforce-repo",
+ "name": "http-mock-lib",
"version": "1.0.0",
"devDependencies": {
"@lwc/eslint-plugin-lwc": "^3.3.0",
diff --git a/package.json b/package.json
index c4052d4..7e2daaf 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
{
- "name": "salesforce-repo",
+ "name": "http-mock-lib",
"private": true,
"version": "1.0.0",
- "description": "Salesforce Repo",
+ "description": "HTTP Mock Lib",
"scripts": {
"lint": "eslint **/{aura,lwc}/**",
"test": "npm run test:unit",
diff --git a/pmd/ruleset.xml b/pmd/ruleset.xml
new file mode 100644
index 0000000..9fceb64
--- /dev/null
+++ b/pmd/ruleset.xml
@@ -0,0 +1,16 @@
+
+
+ Apex PMD ruleset for DML Lib.
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sfdx-project.json b/sfdx-project.json
index 15bed54..7cc45f2 100644
--- a/sfdx-project.json
+++ b/sfdx-project.json
@@ -1,12 +1,21 @@
{
"packageDirectories": [
{
+ "versionName": "ver 1.3.0",
+ "versionNumber": "1.3.0.NEXT",
"path": "force-app",
- "default": true
+ "default": true,
+ "package": "HTTP Mock Lib",
+ "versionDescription": ""
}
],
- "name": "BeyondTheCloud",
- "namespace": "",
+ "name": "http-mock-lib",
+ "namespace": "btcdev",
"sfdcLoginUrl": "https://login.salesforce.com",
- "sourceApiVersion": "57.0"
-}
+ "sourceApiVersion": "65.0",
+ "packageAliases": {
+ "HTTP Mock Lib": "0HoP600000001KHKAY",
+ "HTTP Mock Lib@1.1.0-1": "04tP6000002EJ3FIAW",
+ "HTTP Mock Lib@1.2.0-1": "04tP6000002EJBJIA4"
+ }
+}
\ No newline at end of file
diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts
index cc01c80..a163646 100644
--- a/website/.vitepress/config.mts
+++ b/website/.vitepress/config.mts
@@ -9,8 +9,7 @@ export default defineConfig({
nav: [
{ text: 'Home', link: '/' },
- { text: 'Guide', link: '/getting-started' },
- { text: 'API', link: '/api/' }
+ { text: 'Documentation', link: '/api/' }
],
sidebar: [
@@ -31,27 +30,19 @@ export default defineConfig({
{ text: 'Status Codes', link: '/api/status-codes' },
{ text: 'Headers', link: '/api/headers' }
]
- },
- {
- text: 'Examples',
- items: [
- { text: 'Basic Usage', link: '/examples/basic' },
- { text: 'Multiple Endpoints', link: '/examples/multiple-endpoints' },
- { text: 'Custom Headers', link: '/examples/custom-headers' },
- { text: 'Error Handling', link: '/examples/error-handling' }
- ]
}
],
-
socialLinks: [
- { icon: 'github', link: 'https://github.com/beyond-the-cloud-dev/http-mock-lib' }
+ { icon: 'github', link: 'https://github.com/beyond-the-cloud-dev/http-mock-lib' },
+ {
+ icon: 'linkedin',
+ link: 'https://www.linkedin.com/company/beyondtheclouddev'
+ }
],
-
footer: {
message: 'Part of Apex Fluently',
- copyright: 'Copyright © 2024 Beyond the Cloud'
+ copyright: 'Copyright © 2025 Beyond the Cloud'
},
-
search: {
provider: 'local'
}
diff --git a/website/api/content-types.md b/website/api/content-types.md
index 636e689..b63cabf 100644
--- a/website/api/content-types.md
+++ b/website/api/content-types.md
@@ -2,17 +2,17 @@
Set the Content-Type header for your mocked responses.
-## Built-in Content Types
-
-HTTP Mock Lib provides semantic methods for common content types.
-
-### contentTypeJson()
-
```apex
-HttpMock contentTypeJson() // application/json
+new HttpMock()
+ .whenGetOn('/api/users')
+ .body('{"users": []}')
+ .contentTypeJson()
+ .mock();
```
-Default content type. Used for JSON responses.
+## JSON
+
+`application/json` - Default content type.
```apex
new HttpMock()
@@ -22,13 +22,9 @@ new HttpMock()
.mock();
```
-### contentTypePlainText()
+## Plain Text
-```apex
-HttpMock contentTypePlainText() // text/plain
-```
-
-Plain text responses.
+`text/plain`
```apex
new HttpMock()
@@ -38,13 +34,9 @@ new HttpMock()
.mock();
```
-### contentTypeHtml()
+## HTML
-```apex
-HttpMock contentTypeHtml() // text/html
-```
-
-HTML responses.
+`text/html`
```apex
new HttpMock()
@@ -54,30 +46,21 @@ new HttpMock()
.mock();
```
-### contentTypeCsv()
-
-```apex
-HttpMock contentTypeCsv() // text/csv
-```
+## CSV
-CSV data responses.
+`text/csv`
```apex
new HttpMock()
.whenGetOn('/api/export/users.csv')
- .body('id,name,email\n1,John,john@example.com\n2,Jane,jane@example.com')
+ .body('id,name,email\n1,John,john@example.com')
.contentTypeCsv()
- .header('Content-Disposition', 'attachment; filename="users.csv"')
.mock();
```
-### contentTypeXml()
-
-```apex
-HttpMock contentTypeXml() // application/xml
-```
+## XML
-XML responses.
+`application/xml`
```apex
new HttpMock()
@@ -87,13 +70,9 @@ new HttpMock()
.mock();
```
-### contentTypePdf()
+## PDF
-```apex
-HttpMock contentTypePdf() // application/pdf
-```
-
-PDF document responses.
+`application/pdf`
```apex
Blob pdfData = generatePdfContent();
@@ -105,13 +84,9 @@ new HttpMock()
.mock();
```
-### contentTypeFormUrlencoded()
-
-```apex
-HttpMock contentTypeFormUrlencoded() // application/x-www-form-urlencoded
-```
+## Form URL Encoded
-Form-encoded data.
+`application/x-www-form-urlencoded`
```apex
new HttpMock()
@@ -121,177 +96,27 @@ new HttpMock()
.mock();
```
-## Custom Content Type
+## Custom
-For content types not covered by built-in methods:
+For content types not covered by built-in methods.
```apex
-HttpMock contentType(String contentType)
-```
-
-**Examples:**
-
-```apex
-// YAML
new HttpMock()
.whenGetOn('/api/config')
- .body('key: value\nlist:\n - item1\n - item2')
+ .body('key: value')
.contentType('application/x-yaml')
.mock();
-
-// Protocol Buffers
-new HttpMock()
- .whenGetOn('/api/data')
- .body(protobufBlob)
- .contentType('application/protobuf')
- .mock();
-
-// Custom vendor type
-new HttpMock()
- .whenGetOn('/api/custom')
- .body('{"data": {}}')
- .contentType('application/vnd.mycompany.v1+json')
- .mock();
-```
-
-## Default Content Type
-
-If not specified, HTTP Mock Lib uses `application/json`:
-
-```apex
-// These are equivalent:
-new HttpMock()
- .whenGetOn('/api/users')
- .body('{"users": []}')
- .mock();
-
-new HttpMock()
- .whenGetOn('/api/users')
- .body('{"users": []}')
- .contentTypeJson() // Explicitly set
- .mock();
-```
-
-## Content Type Reference
-
-| Content Type | Method | Usage |
-|-------------|--------|-------|
-| `application/json` | `contentTypeJson()` | JSON data (default) |
-| `text/plain` | `contentTypePlainText()` | Plain text |
-| `text/html` | `contentTypeHtml()` | HTML documents |
-| `text/csv` | `contentTypeCsv()` | CSV data |
-| `application/xml` | `contentTypeXml()` | XML data |
-| `application/pdf` | `contentTypePdf()` | PDF documents |
-| `application/x-www-form-urlencoded` | `contentTypeFormUrlencoded()` | Form data |
-| Custom | `contentType(String)` | Any MIME type |
-
-## Examples
-
-### JSON API Response
-
-```apex
-new HttpMock()
- .whenGetOn('/api/v1/users')
- .body('{"users": [], "total": 0}')
- .contentTypeJson()
- .statusCodeOk()
- .mock();
-```
-
-### CSV Export
-
-```apex
-String csvData = 'Name,Email,Status\n' +
- 'John Doe,john@example.com,Active\n' +
- 'Jane Smith,jane@example.com,Active';
-
-new HttpMock()
- .whenGetOn('/api/export')
- .body(csvData)
- .contentTypeCsv()
- .header('Content-Disposition', 'attachment; filename="export.csv"')
- .statusCodeOk()
- .mock();
-```
-
-### XML SOAP Response
-
-```apex
-String soapResponse =
- '' +
- '' +
- '' +
- 'Success' +
- '' +
- '';
-
-new HttpMock()
- .whenPostOn('/soap/endpoint')
- .body(soapResponse)
- .contentType('application/soap+xml')
- .statusCodeOk()
- .mock();
-```
-
-### Binary File Download
-
-```apex
-Blob fileContent = Blob.valueOf('File content');
-
-new HttpMock()
- .whenGetOn('/api/download/document.pdf')
- .body(fileContent)
- .contentTypePdf()
- .header('Content-Length', String.valueOf(fileContent.size()))
- .header('Content-Disposition', 'attachment; filename="document.pdf"')
- .statusCodeOk()
- .mock();
-```
-
-## Best Practices
-
-1. **Match Real APIs** - Use the same content type as the actual API
-
-2. **Set Explicitly** - Even though JSON is default, explicitly set content type for clarity
-
-3. **Use with Headers** - Combine with other headers like `Content-Disposition` for downloads
-
-4. **Validate Format** - Ensure your body format matches the content type
-
-## Common Patterns
-
-### API with Multiple Formats
-
-```apex
-// JSON endpoint
-new HttpMock()
- .whenGetOn('/api/data?format=json')
- .body('{"data": []}')
- .contentTypeJson()
- .mock();
-
-// XML endpoint
-new HttpMock()
- .whenGetOn('/api/data?format=xml')
- .body('')
- .contentTypeXml()
- .mock();
-```
-
-### File Upload Response
-
-```apex
-new HttpMock()
- .whenPostOn('/api/upload')
- .body('{"fileId": "abc123", "url": "https://cdn.example.com/abc123"}')
- .contentTypeJson()
- .statusCodeCreated()
- .header('Location', '/api/files/abc123')
- .mock();
```
-## See Also
+## Reference
-- [Response Body →](/api/response-body)
-- [Headers →](/api/headers)
-- [Examples →](/examples/basic)
+| Content Type | Method |
+|-------------|--------|
+| `application/json` | `contentTypeJson()` |
+| `text/plain` | `contentTypePlainText()` |
+| `text/html` | `contentTypeHtml()` |
+| `text/csv` | `contentTypeCsv()` |
+| `application/xml` | `contentTypeXml()` |
+| `application/pdf` | `contentTypePdf()` |
+| `application/x-www-form-urlencoded` | `contentTypeFormUrlencoded()` |
+| Custom | `contentType(String)` |
diff --git a/website/api/headers.md b/website/api/headers.md
index 84e26bd..1131d6d 100644
--- a/website/api/headers.md
+++ b/website/api/headers.md
@@ -2,30 +2,18 @@
Add custom HTTP headers to your mocked responses.
-## API
-
-### header()
-
-Add a custom header to the response.
-
-```apex
-HttpMock header(String key, String value)
-```
-
-**Example:**
```apex
new HttpMock()
.whenGetOn('/api/users')
.body('{"users": []}')
.header('X-Total-Count', '42')
.header('X-Page-Number', '1')
- .statusCodeOk()
.mock();
```
## Multiple Headers
-You can chain multiple `.header()` calls to add multiple headers:
+Chain multiple `.header()` calls.
```apex
new HttpMock()
@@ -34,14 +22,10 @@ new HttpMock()
.header('Cache-Control', 'no-cache')
.header('X-Request-ID', 'abc-123-def')
.header('X-API-Version', 'v1')
- .header('X-RateLimit-Remaining', '99')
- .statusCodeOk()
.mock();
```
-## Common Headers
-
-### Cache Control
+## Cache Control
```apex
new HttpMock()
@@ -49,39 +33,23 @@ new HttpMock()
.body('{"data": "cached"}')
.header('Cache-Control', 'max-age=3600')
.header('ETag', '"abc123"')
- .statusCodeOk()
.mock();
```
-### Content Disposition
+## Content Disposition
-For file downloads:
+For file downloads.
```apex
new HttpMock()
.whenGetOn('/api/report.pdf')
.body(pdfBlob)
.contentTypePdf()
- .header('Content-Disposition', 'attachment; filename="monthly-report.pdf"')
- .header('Content-Length', String.valueOf(pdfBlob.size()))
- .statusCodeOk()
- .mock();
-```
-
-### CORS Headers
-
-```apex
-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')
- .statusCodeOk()
+ .header('Content-Disposition', 'attachment; filename="report.pdf"')
.mock();
```
-### Rate Limiting
+## Rate Limiting
```apex
new HttpMock()
@@ -90,11 +58,10 @@ new HttpMock()
.header('X-RateLimit-Limit', '100')
.header('X-RateLimit-Remaining', '95')
.header('X-RateLimit-Reset', '1640000000')
- .statusCodeOk()
.mock();
```
-### Authentication
+## Authentication
```apex
new HttpMock()
@@ -102,26 +69,12 @@ new HttpMock()
.body('{"token": "xyz789"}')
.header('Set-Cookie', 'session=abc123; HttpOnly; Secure')
.header('X-Auth-Token', 'xyz789')
- .statusCodeOk()
- .mock();
-```
-
-### Custom API Headers
-
-```apex
-new HttpMock()
- .whenGetOn('/api/v2/data')
- .body('{"data": []}')
- .header('X-API-Version', 'v2.1.0')
- .header('X-Server-ID', 'server-42')
- .header('X-Request-Duration', '127ms')
- .statusCodeOk()
.mock();
```
-### Location Header
+## Location
-For redirects and created resources:
+For redirects and created resources.
```apex
new HttpMock()
@@ -132,131 +85,15 @@ new HttpMock()
.mock();
```
-## Default Headers
-
-HTTP Mock Lib automatically sets:
-- `Content-Type` (based on your content type method, default: `application/json`)
-
-You can override the Content-Type using `.header()`:
-
-```apex
-new HttpMock()
- .whenGetOn('/api/custom')
- .body('{"data": {}}')
- .header('Content-Type', 'application/vnd.api+json') // Override
- .statusCodeOk()
- .mock();
-```
-
-## Testing Headers
-
-Test that your code properly handles response headers:
-
-```apex
-@IsTest
-static void testRateLimitHeaders() {
- // Arrange
- new HttpMock()
- .whenGetOn('/api/data')
- .body('{"data": []}')
- .header('X-RateLimit-Remaining', '0')
- .statusCodeOk()
- .mock();
-
- // Act
- Test.startTest();
- ApiService service = new ApiService();
- service.getData();
- Test.stopTest();
-
- // Assert
- Assert.isTrue(service.rateLimitReached);
-}
-```
-
-## Best Practices
-
-1. **Use Standard Headers** - Prefer standard HTTP headers (Cache-Control, Content-Type, etc.)
-
-2. **Match Real API** - Include the same headers that the real API returns
-
-3. **Test Header Handling** - Verify your code properly processes important headers
-
-4. **Document Custom Headers** - If using custom `X-` headers, document their purpose
-
-5. **Case Sensitivity** - HTTP headers are case-insensitive, but use standard casing (e.g., `Content-Type` not `content-type`)
-
-## Common Use Cases
-
-### Pagination Metadata
-
-```apex
-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', '; rel="next"')
- .statusCodeOk()
- .mock();
-```
-
-### Error Tracking
-
-```apex
-new HttpMock()
- .whenGetOn('/api/error-prone')
- .body('{"error": "Internal error"}')
- .header('X-Request-ID', 'req-123-456')
- .header('X-Error-Code', 'ERR_500')
- .statusCodeInternalServerError()
- .mock();
-```
-
-### API Versioning
-
-```apex
-new HttpMock()
- .whenGetOn('/api/resource')
- .body('{"data": {}}')
- .header('X-API-Version', '2.0')
- .header('X-Deprecated-In-Version', '3.0')
- .header('Sunset', 'Sat, 31 Dec 2024 23:59:59 GMT')
- .statusCodeOk()
- .mock();
-```
-
-### Content Negotiation
-
-```apex
-new HttpMock()
- .whenGetOn('/api/data')
- .body('{"data": []}')
- .contentTypeJson()
- .header('Content-Language', 'en-US')
- .header('Vary', 'Accept, Accept-Language')
- .statusCodeOk()
- .mock();
-```
-
-## Header Reference
-
-| Header | Purpose | Example Value |
-|--------|---------|---------------|
-| `Cache-Control` | Caching directives | `no-cache`, `max-age=3600` |
-| `Content-Disposition` | File download info | `attachment; filename="file.pdf"` |
-| `Content-Length` | Response size | `1234` |
-| `ETag` | Cache validation | `"abc123"` |
-| `Location` | Redirect/resource URL | `/api/users/123` |
-| `X-RateLimit-*` | Rate limiting info | `100`, `95`, `1640000000` |
-| `X-Request-ID` | Request tracking | `abc-123-def-456` |
-| `X-API-Version` | API version | `v2.1.0` |
-| `Set-Cookie` | Set cookies | `session=abc; HttpOnly` |
-
-## See Also
+## Reference
-- [Content Types →](/api/content-types)
-- [Response Body →](/api/response-body)
-- [Examples →](/examples/custom-headers)
+| Header | Purpose |
+|--------|---------|
+| `Cache-Control` | Caching directives |
+| `Content-Disposition` | File download info |
+| `Content-Length` | Response size |
+| `ETag` | Cache validation |
+| `Location` | Redirect/resource URL |
+| `X-RateLimit-*` | Rate limiting info |
+| `X-Request-ID` | Request tracking |
+| `Set-Cookie` | Set cookies |
diff --git a/website/api/http-methods.md b/website/api/http-methods.md
index dc2de60..bf1d64f 100644
--- a/website/api/http-methods.md
+++ b/website/api/http-methods.md
@@ -2,29 +2,17 @@
Mock different HTTP methods for your endpoints.
-## Supported Methods
-
-HTTP Mock Lib supports all standard HTTP methods:
-
-- `GET` - Retrieve data
-- `POST` - Create resources
-- `PUT` - Update/replace resources
-- `PATCH` - Partially update resources
-- `DELETE` - Remove resources
-- `HEAD` - Get headers only
-- `TRACE` - Debug/diagnostic method
-
-## API
-
-### whenGetOn()
-
-Mock a GET request.
-
```apex
-HttpMock whenGetOn(String endpointToMock)
+new HttpMock()
+ .whenGetOn('/api/v1/users/123')
+ .whenPostOn('/api/v1/comments/')
+ .mock();
```
-**Example:**
+## GET
+
+Retrieve data.
+
```apex
new HttpMock()
.whenGetOn('/api/v1/users/123')
@@ -33,15 +21,10 @@ new HttpMock()
.mock();
```
-### whenPostOn()
-
-Mock a POST request.
+## POST
-```apex
-HttpMock whenPostOn(String endpointToMock)
-```
+Create resources.
-**Example:**
```apex
new HttpMock()
.whenPostOn('/api/v1/users')
@@ -50,15 +33,10 @@ new HttpMock()
.mock();
```
-### whenPutOn()
+## PUT
-Mock a PUT request.
+Update/replace resources.
-```apex
-HttpMock whenPutOn(String endpointToMock)
-```
-
-**Example:**
```apex
new HttpMock()
.whenPutOn('/api/v1/users/123')
@@ -67,15 +45,10 @@ new HttpMock()
.mock();
```
-### whenPatchOn()
-
-Mock a PATCH request.
+## PATCH
-```apex
-HttpMock whenPatchOn(String endpointToMock)
-```
+Partially update resources.
-**Example:**
```apex
new HttpMock()
.whenPatchOn('/api/v1/users/123')
@@ -84,15 +57,10 @@ new HttpMock()
.mock();
```
-### whenDeleteOn()
-
-Mock a DELETE request.
+## DELETE
-```apex
-HttpMock whenDeleteOn(String endpointToMock)
-```
+Remove resources.
-**Example:**
```apex
new HttpMock()
.whenDeleteOn('/api/v1/users/123')
@@ -100,15 +68,10 @@ new HttpMock()
.mock();
```
-### whenHeadOn()
+## HEAD
-Mock a HEAD request.
+Get headers only.
-```apex
-HttpMock whenHeadOn(String endpointToMock)
-```
-
-**Example:**
```apex
new HttpMock()
.whenHeadOn('/api/v1/users/123')
@@ -117,15 +80,10 @@ new HttpMock()
.mock();
```
-### whenTraceOn()
-
-Mock a TRACE request.
+## TRACE
-```apex
-HttpMock whenTraceOn(String endpointToMock)
-```
+Debug/diagnostic method.
-**Example:**
```apex
new HttpMock()
.whenTraceOn('/api/v1/debug')
@@ -142,25 +100,21 @@ You can mock multiple HTTP methods in a single test:
@IsTest
static void testCrudOperations() {
new HttpMock()
- // Create
.whenPostOn('/api/v1/users')
.body('{"id": "123"}')
.statusCodeCreated()
- // Read
.whenGetOn('/api/v1/users/123')
.body('{"id": "123", "name": "John"}')
.statusCodeOk()
- // Update
.whenPutOn('/api/v1/users/123')
.body('{"updated": true}')
.statusCodeOk()
- // Delete
.whenDeleteOn('/api/v1/users/123')
.statusCodeNoContent()
.mock();
Test.startTest();
- // Your CRUD operations here
+ // Your callout here
Test.stopTest();
}
```
@@ -185,24 +139,3 @@ new HttpMock()
.whenGetOn('https://api.example.com/v1/users') // ❌ Wrong - includes domain
.mock();
```
-
-## Best Practices
-
-1. **Use Full Paths** - Include API version in the path: `/api/v1/users` instead of `/users`
-
-2. **Match HTTP Semantics** - Use the correct method for the operation:
- - `GET` for retrieval
- - `POST` for creation
- - `PUT` for full updates
- - `PATCH` for partial updates
- - `DELETE` for removal
-
-3. **Test All Methods** - If your service uses multiple HTTP methods, test them all
-
-4. **RESTful Patterns** - Follow REST conventions in your mocks to match real APIs
-
-## See Also
-
-- [Status Codes →](/api/status-codes)
-- [Response Body →](/api/response-body)
-- [Examples →](/examples/basic)
diff --git a/website/api/index.md b/website/api/index.md
index 677629a..521c261 100644
--- a/website/api/index.md
+++ b/website/api/index.md
@@ -142,17 +142,3 @@ If not specified, HTTP Mock Lib uses these defaults:
## Thread Safety
HTTP Mock Lib uses Salesforce's built-in `Test.setMock()` mechanism, which is thread-safe within test context.
-
-## Best Practices
-
-1. **Call `.mock()` last** - Always call `.mock()` as the final method to activate the mock
-2. **One mock per test** - Create a new HttpMock for each test method
-3. **Clear endpoint paths** - Use full paths like `/api/v1/users` instead of `/users`
-4. **Meaningful status codes** - Use appropriate status codes to test error handling
-5. **Realistic responses** - Use actual response formats from your APIs
-
-## See Also
-
-- [HTTP Methods →](/api/http-methods)
-- [Status Codes →](/api/status-codes)
-- [Examples →](/examples/basic)
diff --git a/website/api/response-body.md b/website/api/response-body.md
index f3ee7b4..f185116 100644
--- a/website/api/response-body.md
+++ b/website/api/response-body.md
@@ -2,41 +2,29 @@
Configure the response body for your mocked HTTP calls.
-## Overview
-
-HTTP Mock Lib supports three types of response bodies:
-- **String** - Raw string data
-- **Object** - Apex objects (automatically serialized to JSON)
-- **Blob** - Binary data
-
-## API
-
-### body(String)
-
-Set a string response body.
-
-```apex
-HttpMock body(String body)
-```
-
-**Example:**
```apex
new HttpMock()
.whenGetOn('/api/users')
.body('{"id": "123", "name": "John Doe"}')
- .statusCodeOk()
.mock();
```
-### body(Object)
+## String
-Set an object response body. The object will be JSON-serialized automatically.
+Raw string data (JSON, XML, plain text, etc.).
```apex
-HttpMock body(Object body)
+new HttpMock()
+ .whenGetOn('/api/v1/token')
+ .body('{"access_token": "abc123", "expires_in": 3600}')
+ .statusCodeOk()
+ .mock();
```
-**Example:**
+## Object
+
+Apex objects are automatically serialized to JSON.
+
```apex
Map response = new Map{
'id' => '123',
@@ -51,15 +39,10 @@ new HttpMock()
.mock();
```
-### body(Blob)
-
-Set a binary response body.
+## Blob
-```apex
-HttpMock body(Blob body)
-```
+Binary data for files.
-**Example:**
```apex
Blob pdfData = Blob.valueOf('PDF content here');
@@ -71,71 +54,19 @@ new HttpMock()
.mock();
```
-## Examples
-
-### JSON String Response
-
-```apex
-new HttpMock()
- .whenGetOn('/api/v1/token')
- .body('{"access_token": "abc123", "expires_in": 3600}')
- .contentTypeJson()
- .statusCodeOk()
- .mock();
-```
-
-### Map Response
-
-```apex
-Map tokenResponse = new Map{
- 'access_token' => 'abc123',
- 'expires_in' => '3600'
-};
-
-new HttpMock()
- .whenGetOn('/api/v1/token')
- .body(tokenResponse)
- .statusCodeOk()
- .mock();
-```
+## Static Resource
-### List Response
+Load response body from a Static Resource.
```apex
-List