Testing HTTP Callouts using SFCraft MockServer: Part 1

Recently I published my MockServer lib. The history behind it is simple — for years I’ve been writing HttpCalloutMock implementations for each new project. I don’t want to do it anymore and instead, I’ve developed a single library where you can easily add stuff.

I split this article into 2 parts. In this first part, I’ll compare the Salesforce provided example of HttpCalloutMock and the same code tested with the MockServer lib. Let’s start!

First, let’s take a look at the code under test:

public class CalloutClass {
public static HttpResponse getInfoFromExternalService() {
HttpRequest req = new HttpRequest();
req.setEndpoint('http://example.com/example/test');
req.setMethod('GET');
Http h = new Http();
HttpResponse res = h.send(req);
return res;
}
}

This is a simple class with a single static method that does a callout to the http://example.com/example/test endpoint with method GET. That means we need to mock this endpoint. Below is the pure HttpCalloutMock example provided by Salesforce.

Salesforce Example of HttpCalloutMock

First, we have the HttpCalloutMock implementation. This part will be absent in the MockServer-based test since that’s what MockServer actually incapsulates.

@isTest
public class MockHttpResponseGenerator implements HttpCalloutMock {
// Implement this interface method
public HTTPResponse respond(HTTPRequest req) {
// Optionally, only send a mock response for a specific endpoint
// and method.
System.assertEquals('http://example.com/example/test', req.getEndpoint());
System.assertEquals('GET', req.getMethod());
// Create a fake response
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setBody('{"example":"test"}');
res.setStatusCode(200);
return res;
}
}

As you can see in this implementation we only have a single endpoint with a single supported method, which is checked by assertions. This means adding any new endpoints or responses will require changing this code. In this specific example, we’re fine with hardcoding it, but in a real project, it’s rarely the case.

The second part is the test class itself:

@isTest
private class CalloutClassTest {
@isTest
private static void testCallout() {
// Set mock callout class
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
// Call method to test.
// This causes a fake response to be sent
// from the class that implements HttpCalloutMock.
HttpResponse res = CalloutClass.getInfoFromExternalService();
// Verify response received contains fake values
String contentType = res.getHeader('Content-Type');
System.assert(contentType == 'application/json');
String actualValue = res.getBody();
String expectedValue = '{"example":"test"}';
System.assertEquals(actualValue, expectedValue);
System.assertEquals(200, res.getStatusCode());
}
}

Nothing really interesting here. We just create an instance of the MockHttpResponseGenerator and set it as mock. Then we do assertions to make sure we get what we want.

The main problem with this approach is it’s hardly scalable. If you want to add new endpoints or new methods to your HttpCalloutMock implementation you usually end up with either a map of responses or a ton of if or switch statements.

Mocking responses with MockServer

Let’s now take a look at how the MockServer lib powers the HTTP testing. I put all the test implementation into 2 methods in the same test class and will describe them one by one.

First, let’s configure the mock server. I describe each line in the comments — they should give you an idea of how it works.

@isTest
private class CalloutClassTest {
private static void configureMockServer() {
// Create api resource
// Resources are responsible for providing correct mock responses for a method requested
sfcraft_MockAPIResource apiResource = new sfcraft_MockAPIResource();
// add a response
apiResource.setResponse(
// method to respond to. Basically this can be any HTTP method (or any string at all!)
'GET',
// code to respond with
200,
// response body (optional, empty string by default)
'{"example":"test"}',
// response headers (optional)
new Map<String, String> {
'Content-Type' => 'application/json'
}
);
// Instantiating a server
sfcraft_MockServer server = new sfcraft_MockServer();
// Setting server as mock
server.setAsMock();
// Adding resource to server and pointing it to a specific endpoint
server.addEndpoint('http://example.com/example/test', apiResource);
}
}

I only added the 200 code here as in the original example. By default, the MockServer will look for code 200 and respond with it. In case you don’t have code 200 on a resource, it will throw an error. You can also tell an endpoint to respond with a different code. There are some examples in the repo and I will also cover it in the next post.

Now let’s adjust the test method itself:

@isTest
private class CalloutClassTest {
private static void configureMockServer() {...
}
@isTest
private static void testCallout() {
// Instead of setting mock callout class, we just call the configuration method
configureMockServer();

// Call method to test.
// This causes a fake response to be sent
// from the class that implements HttpCalloutMock.
HttpResponse res = CalloutClass.getInfoFromExternalService();
// Verify response received contains fake values
String contentType = res.getHeader('Content-Type');
System.assert(contentType == 'application/json');
String actualValue = res.getBody();
String expectedValue = '{"example":"test"}';
System.assertEquals(actualValue, expectedValue);
System.assertEquals(200, res.getStatusCode());
}
}

As you can see in the test method itself we only changed one line. The test is still green, you can try and do this yourself!

But what’s important you can easily add new endpoints by just providing new responses, resources, or endpoints by simply adding additional configurations:

private static void configureMockServer() {
// Original Resource
sfcraft_MockAPIResource apiResource = new sfcraft_MockAPIResource();
apiResource.setResponse(
'GET',
200,
'{"example":"test"}',
new Map<String, String> {
'Content-Type' => 'application/json'
}
);
apiResource.setResponse(
// Let's add POST method
'POST',
200,
'{"example":"test"}',
new Map<String, String> {
'Content-Type' => 'application/json'
}
);
apiResource.setResponse(
'GET',
// Let's add another code!
400,
'{"error":"true"}',
new Map<String, String> {
'Content-Type' => 'application/json'
}
);
// let's create another response
sfcraft_MockAPIResource anotherApiResource = new sfcraft_MockAPIResource();
anotherApiResource.setResponse(
'GET',
200,
'{"anotherExample":"test"}',
new Map<String, String> {
'Content-Type' => 'application/json'
}
);
// Instantiating a server
sfcraft_MockServer server = new sfcraft_MockServer();
// Setting server as mock
server.setAsMock();
// Adding resource to server
server.addEndpoint('http://example.com/example/test', apiResource);
server.addEndpoint('http://example.com/anotherExample/test', anotherApiResource);
}

As you can see with the mock server you don’t need to create additional classes to maintain, and you get more scalability too! Try it yourself and let me know what do you think! Here’s the repo link

Salesforce Craftsman | Moscow Salesforce User Group Leader | https://www.salesforcecraft.dev