2

I have a class that is called before the handler method of a trigger to check and see if a Custom Metadata Field is checked and therefore the code in the trigger handler should not be run. I can verify this works if you manually check the box in Manage Custom Metadata Settings, however I need to get increased coverage on it. Currently the tests for the trigger provide enough coverage to deploy, but barely enough. I need to be able to feed the value of true to the Custom Metadata within a test class. So, at Daniel Balinger's suggestion, I am attempting to do that using a mock call out as well as the JSON serialize/deserialize magic that was suggested by Patlatus and the expanded example from @andrew-fawcett here: Unit Test - Creating Custom Metadata I finally got the test to run without errors, but from the debug log and the lack of code coverage, I can tell it's not working correctly.

Here is my code; any help is appreciated, as I only did a Trailhead module that involved mock call outs a year and a half ago, and have never serialized/deserialized JSON.

The Static Resource:

{
    "Disa\ble_Accou\n\t_c_AU__c": "\t\rue",
    "Disa\ble_A\t\tachme\n\t_AI__c": "\t\rue",
    "Disa\ble_A\t\tachme\n\t_AU__c": "\t\rue",
    "Disa\ble_Coop_Dolla\rs_c_AD__c": "\t\rue",
    "Disa\ble_Coop_Dolla\rs_c_AI__c": "\t\rue",
    "Disa\ble_Coop_Dolla\rs_c_AU__c": "\t\rue",
    "Disa\ble_Speci\fica\tio\n_C\redi\t_c_AI__c": "\t\rue",
    "Disa\ble_Speci\fica\tio\n_C\redi\t_c_BI__c": "\t\rue",
    "Disa\ble_Use\r_AI__c": "\t\rue"
}

The Calloout Class:

public class CalloutToStaticMetadataClass {
    public static HttpResponse getCustomMetadata(String endpoint){
        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setMethod('GET');
        Http h = new Http();
        HttpResponse res = h.send(req);
        return res;
    }
}

The test:

@isTest
public class TriggersTurnedOff_Test {     
    @isTest
    public static void AreTheTriggersTurnedOff(){
        Map<String, Object> metadataSettings = new Map<String, Object>();
        // Use StaticResourceCalloutMock built-in class to
        // specify fake response and include response body 
        // in a static resource.
        StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
        mock.setStaticResource('CustomMetadataStaticResource_Escaped');
        mock.setStatusCode(200);
        mock.setHeader('Content-Type', 'application/json');

        // Set the mock callout mode
        Test.setMock(HttpCalloutMock.class, mock);

        // Call the method that performs the callout
        HTTPResponse res = CalloutToStaticMetadataClass.getCustomMetadata('http://example.com/example/test'); 

        if(res.getStatusCode() == 200){
            // metadataSettings = (Map<String, String>) JSON.deserializeUntyped(res.getBody());
            metadataSettings = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            System.debug('&*&**&**&*& metadataSettings &*&**&**&*&' + metadataSettings);
        }
        Disable_Triggers__mdt triggerCheckboxes = new Disable_Triggers__mdt();
        triggerCheckboxes = (Disable_Triggers__mdt) JSON.deserialize(JSON.serialize(metadataSettings), Disable_Triggers__mdt.class);
        System.debug('^&^&^&^&^ triggerCheckboxes ^&^&^&^&^' + triggerCheckboxes);
    }

/*    
    public static void deserializeMetadata(){

        Disable_Triggers__mdt triggerCheckboxes = new Disable_Triggers__mdt();
        triggerCheckboxes = (Disable_Triggers__mdt) JSON.deserialize(JSON.serialize(metadataSettings), Disable_Triggers__mdt.class);
        System.debug('^&^&^&^&^ triggerCheckboxes ^&^&^&^&^' + triggerCheckboxes);
    }
*/

}

Picture of the resulting debug log.

The Custom Metadata Object.

Trigger:

trigger AccountTrigger on Account (after update) {
    String customMetadataField = 'Disable_Account_c_AU__c';
    Schema.SObjectType accountType = Schema.Account.sObjectType;
    accountType = Account.sObjectType;
    Boolean enableHandler = AccountTriggerCheckDisabled.IsHandlerDisabled(accountType, customMetadataField);
    if(enableHandler){
        new AccountTriggerHandler().run();
    }
} 
  • What does the trigger itself look like? In particular, show how it interacts with the Custom Metadata Type. – Daniel Ballinger May 31 '18 at 0:21
  • Here's an example of one: trigger AccountTrigger on Account (after update) { String customMetadataField = 'Disable_Account_c_AU__c'; Schema.SObjectType accountType = Schema.Account.sObjectType; accountType = Account.sObjectType; Boolean enableHandler = AccountTriggerCheckDisabled.IsHandlerDisabled(accountType, customMetadataField); if(enableHandler){ new AccountTriggerHandler().run(); } } – AstroLovesCodey May 31 '18 at 0:26
  • Sorry for formatting. Neither ctrl-k nor indenting 4 spaces helped it. – AstroLovesCodey May 31 '18 at 0:28
  • 1
    If possible edit it into the original question. It will make it easier for others to follow. I'm thinking you can use a Stub to control the results of AccountTriggerCheckDisabled.IsHandlerDisabled(..) from the test class. That way it doesn't matter what the CMDT is actually set to. – Daniel Ballinger May 31 '18 at 0:30
  • So, something like this: developer.salesforce.com/docs/atlas.en-us.apexcode.meta/… ??? I'm sorry, I'm not really experienced with stubs. Also, I still would like to know what's going on with the metadata call if you (or anyone) have time to look at it. – AstroLovesCodey May 31 '18 at 0:41
3

My understanding of the problem is that you want to be able to define the results of the following line in the trigger from the test methods:

 Boolean enableHandler = AccountTriggerCheckDisabled.IsHandlerDisabled(accountType, customMetadataField);

I assume the class looks something like:

public class AccountTriggerCheckDisabled {
    public static boolean IsHandlerDisabled(Schema.SObjectType classType, string customMetadataField) {
        // Query the Disable_Triggers__mdt CMDT based on the params and return a boolean
        return true;
    }
}

There are two ways to do this from a test method.

1. The Hacky Way

At this risk of ire Kevin Poorman you could just hack something into AccountTriggerCheckDisabled that lets the test class control the response directly.

public class AccountTriggerCheckDisabled {
    // This would need to be keyed by the Schema.SObjectType as well.
    // I'm only using the string to make this example quicker.
    private Map<string, boolean> testResponse = new Map<string, boolean>();

    @TestVisible
    private static void defineTestResponse(Schema.SObjectType classType, string customMetadataField, boolean response) {
        testResponse.put(customMetadataField, response);
    } 

    public static boolean IsHandlerDisabled(Schema.SObjectType classType, string customMetadataField) {

        if(Test.isRunningTest() && !testResponse.isEmpty()) {
            if(testResponse.containsKey(customMetadataField)) {
                return testResponse.get(customMetadataField);
            }
        }

        // Query the Disable_Triggers__mdt CMDT based on the params and return a boolean
        return true;
    }
}

Now your test class can define the response from IsHandlerDisabled as required without even touching the Disable_Triggers__mdt CMDT.

@isTest
public class TriggersTurnedOff_Test {     
    @isTest
    public static void AreTheTriggersTurnedOff(){
        boolean mockTestResponseYouWantForTheTest = true;
        AccountTriggerCheckDisabled.defineTestResponse(Schema.Account.sObjectType, 'Disable_Account_c_AU__c', mockTestResponseYouWantForTheTest);

        //Now update an Account

        // When the trigger fires enableHandler will come back as true
    }
}

2. The more complicated but overall better way.

Apex now has System.StubProvider for scenarios just like this one. It is more complicated to implement, but it essentially allows you to define an Apex class that will stand in for the actual AccountTriggerCheckDisabled class in the test context. This stub class can return whatever you want when the test is running.

You can see an example of this in Month of Testing: Advanced Topics in Salesforce Unit Testing (Part 3 of 3) under the Stubbing: with great flexibility comes great architecture heading.

Why is this approach better? The short answer is you don't need to have code specific to testing the class in the target class itself.

It is more complicated as you will need a mechanism to swap out the actual AccountTriggerCheckDisabled class with your stub class.

  • 2
    ApexMocks (GitHub) provides a nice wrapper around System.Stubprovider – cropredy May 31 '18 at 0:54
  • The StubProvider Interface is exactly what I was looking for and Kevin's blog explains it really well! Thank you for explaining the 'hacky' way as well as pointing me to his blog! I really appreciate it! :) – AstroLovesCodey Jun 1 '18 at 15:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.