HTTP Callouts

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!

Advertisement

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.

dont miss out iconDon'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!

dont miss out iconCheck 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

Popular Salesforce Blogs