Testing HTTP Callouts using SFCraft MockServer: Part 1 | Salesforce Help Guide
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 an 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 the method
GET
. That means we need to mock this endpoint. Below is the pure HttpCalloutMock
the 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.
Don't forget to check out: Salesforce Platform API Versions 21.0 thru 30.0
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 them 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!
Check out an amazing Salesforce Tutorial video here: A Beginner's Guide to Getting Started with Salesforce APIs Using Postman
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
Responses