11

I have an after insert trigger that invokes queueable Apex which makes a callout. The trigger and callout work fine in the UI and I don't get any errors in normal operation.

However, my unit test keeps failing with the "uncommitted work pending" error despite all my attempts to separate the callout. I've used a testVisible variable to disable the callout from the trigger so that I can invoke the queueable job explicitly in a startTest/stopTest block after the DML that fires the trigger.

I've pored over the callout class to make sure it's not doing any DML beforehand. Anyway, if it was doing DML, I should get the uncommitted work pending errors in the UI, right? I've tried API version 37 and version 41 but the unit test still fails.

I've had a case open with Salesforce premier support for 6 weeks now but haven't made any progress. Any suggestions?

The unit test:

@isTest
static void testCallout() {
    // Disable the callout while setting up data.
    CCx_EHR_CMCT_Services.isQueueSendEnabled = false;

    // Set up the data and execute the DML.
    / ...
    insert act;

    // Set up the http mock
    // ...

    // Now enable and execute the callout
    Test.startTest();
    CCx_EHR_CMCT_Services.isQueueSendEnabled = true;
    Test.setMock(HttpCalloutMock.class, mock);
    System.enqueueJob(new QueueSendToRedox(new List<ActivityHL__c>{act}, desk)));
    Test.stopTest();

}

Trigger:

trigger HL_ActivityHL_c on ActivityHL__c (before insert, before update, after insert) {
    if (Trigger.isAfter) {
        if (Trigger.isInsert) {
            CCx_ClientDataDomain.sendEHRClinicalComm(Trigger.new);
        }
    }
}

Trigger handler class:

public without sharing class CCx_ClientDataDomain {
    public static void sendEHRClinicalComm(List<ActivityHL__c> triggerNew) {
    // do some processing here.
    // ...
    // then queue the callout job.
    System.enqueueJob(new CCx_EHR_CMCT_Services.QueueSendToRedox(
            deskActivities.get((Id) desk.getDeskId()), desk));
    }
}

Queueable job class with callout method:

public with sharing class CCx_EHR_CMCT_Services {
    // isQueueSendEnabled is an attempted workaround for unit tests
    @testVisible
    private static Boolean isQueueSendEnabled = true;

    public class QueueSendToRedox implements Queueable, Database.AllowsCallouts {
        private List<ActivityHL__c> activities;
        private CCx_DeskHierarchySelector.Desk desk;
        private Boolean isEnabled;

        public QueueSendToRedox(List<ActivityHL__c> activities, CCx_DeskHierarchySelector.Desk desk) {
            this.activities = activities;
            this.desk = desk;
            this.isEnabled = isQueueSendEnabled;
        }

        public void execute (QueueableContext context) {
            if(this.isEnabled) sendClinicalCommToRedox(activities, desk);
        }
    }

    public static void sendClinicalCommToRedox(List<ActivityHL__c> activities, CCx_DeskHierarchySelector.Desk desk) {
        // Set up data for the callout.  No DML actions are executed before the callout.
        HttpRequest req = buildRequest();
        // ...

        // Callout
        Http http = new Http();
        HttpResponse resp = http.send(req);
    }
}
10

You'll need to test the method directly, and test the Queueable separately. The easiest way to do so is a static flag.

public class MyQueueable implements Queueable
    @TestVisible static Boolean makeCallout = true;
    public void execute(QueueableContext context)
    {
        if (makeCallout) performCallout();
    }
    public static void performCallout()
    {
        // make callout here
    }
}

Testing the job:

Test.startTest();
    MyQueueable.makeCallout = false;
    // enqueue job
Test.stopTest();

Then test your method which actually makes the callout separately:

Test.startTest();
    MyQueueable.performCallout();
Test.stopTest();

The reason you get this error is that when you enqueue the job, that counts as a DML Operation. Then when you call Test.stopTest(), it runs synchronously, making a callout within the same transaction.

  • 1
    Many thanks @Adrian! This has been a big headache for weeks and Salesforce premier support couldn't come up with the answer. – David Cheng Dec 29 '17 at 0:51
  • 3
    Looks like a bug, Queuable Apex/future was designed to allow callouts after DML, But testing framework kinda defeats its purpose. Today I wasted 4-5 hours investigating this, it should be part of the documentation. – Pranay Jaiswal Mar 1 '18 at 1:47

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.