Writing Test Coverage

Everyone’s favorite subject: test coverage! Writing test coverage for your Apex classes can be a bit of a pain, but hopefully this page will get you set up for success.

We’ll break down how to get your hands on different things you need, and provide sample code for both Adapter and Filter test coverage.

Common Valence Classes

LinkContext

You will need an instance of LinkContext in just about every test. Luckily, they’re super simple to construct.

valence.LinkContext context = new valence.LinkContext();
context.linkTargetName = 'Account';
context.testingMode = true;
// etc etc

You can usually just set whichever properties you know you are going to depend on in the code you are exercising.

Mapping

Often you’re constructing Mapping instances because you want to set the mappings property on LinkContext.

Apex class: ValenceTestUtil
global static Mapping createTestMapping(String name, String conciseSourcePropertyPath, String conciseTargetPropertyPath, String configuration);

global static Mapping createTestMapping(String name, List<String> normalizedSourcePropertyPath, List<String> normalizedTargetPropertyPath, String configuration);

The keys in the mappings property should match the names of each mapping.

valence.LinkContext context = new valence.LinkContext();
context.mappings = new Map<String, valence.Mapping>{
        'one' => valence.ValenceTestUtil.createTestMapping('one', 'first_name', 'FirstName', null),
        'two' => valence.ValenceTestUtil.createTestMapping('two', 'last_name', 'LastName', null)
};

RecordInFlight

RecordInFlight is the last of the four most-common Valence classes you’ll need to work with in your tests.

Apex class: ValenceTestUtil
global static RecordInFlight createTestRecordInFlight(Map<String, Object> original, Map<String, Object> properties);

Use the static method above if you want to build RecordInFlight instances that look like they’ve already been processed (i.e. have properties set already).

If you just want a basic RecordInFlight instance, you can instantiate it normally:

valence.RecordInFlight record = new valence.RecordInFlight(new Map<String, Object> {'first_name' => 'Fred', 'last_name' => 'Johnson'});

Sample Code

To see real Adapters and Filters and test coverage for them, check out our open-source extensions:

Mocking API Responses for Adapter Testing

In order to properly test your API callouts you’ll want to familiarize yourself with Apex callout mocking. We have some patterns we follow with these that we’re happy to share.

Where to Mock

It saves a bit of mental effort if your mocks and tests are in the same class, like this.

@IsTest
private class MyAdapterTests implements HttpCalloutMock {

        public HTTPResponse respond(HTTPRequest request) {
                // various responses
        }

        @IsTest static void testBehavior() {
                Test.setMock(HttpCalloutMock.class, new MyAdapterTests());

                // test stuff
        }
}

There are a couple pretty good patterns for selecting which mock response is appropriate.

@IsTest
private class MyAdapterTests implements HttpCalloutMock {

        private String mockBody;
        private Integer statusCode;

        private static final String FAILURE_RESPONSE = '<response><error><message>Explosions and fire</message></error></response>';

        private MyAdapterTests(String mockBody, Integer statusCode) {
                this.mockBody = mockBody;
                this.statusCode = statusCode;
        }

        public HTTPResponse respond(HTTPRequest request) {
                HttpResponse response = new HttpResponse();
                response.setStatusCode(statusCode);
                response.setBody(mockBody);
                return response;
        }

        @IsTest static void testErrorResponse() {
                Test.setMock(HttpCalloutMock.class, new MyAdapterTests(FAILURE_RESPONSE, 400));

                MyAdapter adapter = new MyAdapter();

                // test stuff
        }
}
@IsTest
private class MyAdapterTests implements HttpCalloutMock {

        private static final String FAILURE_RESPONSE = '<response><error><message>Explosions and fire</message></error></response>';

        public HTTPResponse respond(HTTPRequest request) {
                HttpResponse response = new HttpResponse();
                if(req.getEndpoint().startsWith('callout:errorResponse')) { // asking for error response
                        response.setHeader('Content-Type', 'application/json;charset=UTF-8');
                        response.setStatusCode(400);
                        response.setStatus('BAD REQUEST');
                        response.setBody(FAILURE_RESPONSE);
                }
                return response;
        }

        @IsTest static void testErrorResponse() {
                Test.setMock(HttpCalloutMock.class, new MyAdapterTests());

                MyAdapter adapter = new MyAdapter();
                adapter.setNamedCredential('errorResponse'); // use named credential names to drive which HTTPResponse we get back

                // test stuff
        }
}

Gathering Mock Responses

Most of the work setting up your mocking is getting realistic response body values for the API you’re writing against. We use either of these approaches:

  • Interact with the API using Postman calling various endpoints, and strip whitespace from the response with an online tool so we can make it one line in our test class.

  • Add debug logging to your class and actually use it a bit in Valence (or invoke it using temporary static methods), then grab the logs, strip whitespace, and save to your test class.

Be patient gathering these, and comprehensive. Try to get all the variations of error messages, and all the variations of responses you might expect. For example, when mocking a record fetch:

  • Have a mock response with no records

  • Have a mock response with some records, but enough to fit under the context.batchSizeLimit in your test (set it to something low like 5 for the test)

  • Have a mock response with more records than will fit in one batch

It’s not unusual for us to have 15-30 different API responses mocked in our test class.