2

I'm having difficulty passing a paramater to a HttpCalloutMock class. I have a trigger that calls a future callout on insert and on the response I'm receiving the record id so I can do the relevant record updates. this works fine but when I try to test it I can't pass the Id of the record to the mock class as I'm declaring it before I insert it. Am I going about it the wrong way or is there a workaround? here is the code snippet:

@isTest
public static void testNewCaseInsert(){

    Id user = [SELECT id FROM User WHERE FirstName = 'TestUser' LIMIT 1].id;

    Id myPlusRTypeId    = [SELECT id FROM RecordType WHERE developerName = 'Test_Plus' LIMIT 1].id;

    Id contactId = [SELECT id FROM Contact WHERE FirstName = 'Contact' LIMIT 1].id;

    Case caseMPlus        = new Case(

        RecordTypeId         =  myPlusRTypeId,
        OwnerId              =  user,
        Status               = 'New',
        Priority             = 'Medium',
        ContactId            =  contactId,
        Origin               = 'Email',
        Subject              = 'Subject Mail Plus',
        Description          = 'watermelons',
     );

    test.setMock(HttpCalloutMock.class, new MockResponse(caseMPlus.id));//I can't pass case Id without inserting it first

    test.startTest();
        insert caseMPlus;
    test.stopTest();

   }

And here is the Mock class:

@isTest
public class LimitlessMockResponse implements HttpCalloutMock {

    protected Id caseId;

    public LimitlessMockResponse(Id caseId){

        this.caseId = caseId;

    }


    public HTTPResponse respond(HTTPRequest req) {

        System.assertEquals('callout:Limitless', req.getEndpoint());
        System.assertEquals('POST', req.getMethod());

        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        //trying to pass the caseId into the body to assert against the response
        res.setBody('{"count": 1,"success": 1,"failed": 0,"messages": [{"tenantMsgId":'+'"'+caseId+'","rejectReasonCode": "no group found","processCode": "ACC"}]}');
        res.setStatusCode(200);
        return res;
    }

}

Here is the logic part:

public static void handleResponse(String rBody){

    List<Case>updateCases = new List<Case>();

    LimitlessDataResponse dResponse = LimitlessDataResponse.parse(rBody);

        //Loop through the cases on the response and update 
        for(DataResponse.Messages r : dResponse.messages){
         //Here is where I'm using the case Id from the response to matchup against
                Case c = new case (id = r.tenantMsgId,
                                   Sync_Status__c = r.processCode == 'ACC' ? 'Synced' : 'Failed',
                                   Sync_Failed_Reason__c = r.rejectReasonCode,
                                   Retry__c = FALSE
                                   );

                system.debug('ResponseData:'+ r.processCode +'  '+r.tenantMsgId);

                updateCases.add(c);
        }   
    if(!updateCases.isEmpty()){

        try{

            update updateCases;

        }catch(DmlException e){

            system.debug('There was an error handling the reponse:'+ e.getMessage());
        }
    }

}
  • Why does the Id matter? – Adrian Larson Nov 20 '17 at 22:53
  • i'm posting case details on another system, and with the resulting response I want to update the case with a "Success" or "Fail" field. I added the code snippet for that part of the logic at the end – JPG Nov 20 '17 at 22:58
3

Test the fact that your trigger makes a callout and the handling of that callout separately. A more testable structure would look something like:

public with sharing class CaseTriggerHandler
{
    @TestVisible static Boolean bypassTrigger;

    final List<Case> newRecords;
    final Map<Id, Case> oldMap;
    public CaseTriggerHandler(List<Case> newRecords, Map<Id, Case> oldMap)
    {
        this.newRecords = newRecords;
        this.oldMap = oldMap;
    }

    public void afterInsert()
    {
        if (bypassTrigger) return;

        // optionally filter a collection of records
        // for which a callout is necessary
        Set<Id> recordIds = new Map<Id, Case>(newRecords).keySet();
        CaseService.makeSomeCallout(recordIds);
    }
}

Your trigger would call this handler somewhat like so:

CaseTriggerHandler handle = new CaseTriggerHandler(trigger.new, trigger.oldMap);

if (trigger.isAfter)
{
    if (trigger.isInsert) handle.afterInsert();
}

Now testing the trigger itself is a cinch. Just make sure a callout is made.

@IsTest
class CaseTriggerTests
{
    static testmethod void testTrigger_InsertCallout()
    {
        List<Case> records = new List<Case>();
        // populate collection with a bunch of data

        Test.startTest();
            // set any mock
            insert records;
            Integer callouts = Limits.getCallouts();
        Test.stopTest();

        system.assertEquals(1, callouts, 'A singe callout should be made');
    }
}

As for testing the callout itself, you can disable the trigger, insert some records, and then make your call and verify the handler method does what it is supposed to.

// testing CaseService
static testmethod void testMakeSomeCallout()
{
    CaseTriggerHandler.bypassTrigger = true;

    Case record = new Case(/*requisite data*/);
    insert record;

    CaseTrigger.bypassTrigger = false;
    // best to turn the trigger back on

    Test.startTest();
        // set your mock with the proper Id here here
        CaseService.makeSomeCallout(new Set<Id>{record.Id});
    Test.stopTest();

    // assert the record was updated correctly
}

Please note you have an empty catch block. That is a very bad idea and may cause you to spend hours tearing your hair out trying to figure out why an isolated test is failing. Always make sure your catch blocks have side-effects which you can assert against. Otherwise remove the catch block entirely.

| improve this answer | |
  • Thanks for the help! this worked and you made it simple to understand – JPG Nov 29 '17 at 17:07
  • @JPG Glad I could help. Please do note that last paragraph and remove your empty catch block. Otherwise some developer in 6 months is going to be very upset (maybe yourself). – Adrian Larson Nov 29 '17 at 19:02

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.