2

We have method EnrollmentUtil.createNewEnrollment which contains a http callout on create of custom object Enrolment__c, and a test class to test this method. I'm trying to figure out how this class was passing 2 months ago when it was deployed to production, but with no changes since then it is now failing in production and sandboxes. On running EnrollmentUtil_Test we're getting the "You have uncommitted work pending. Please commit or rollback before calling out" error on EnrollmentUtil line 57 when the http callout is being made.

Apex Class code snippet

public class EnrollmentUtil {

public static string response {get;set;}

public static boolean disableTrigger {get{if(disableTrigger == null) disableTrigger = false; return disableTrigger;} set;} 


public static void newEnrollment(map<Id,Enrolment__c> mapEnrollments) {
    if(Test.isRunningTest() && Limits.getFutureCalls() >= Limits.getLimitFutureCalls()) {
        system.debug(LoggingLevel.Error, 'Future method limit reached. Skipping...');
    } else {
        if(!Test.isRunningTest() && System.IsBatch() == false && System.isFuture() == false) {
            createNewEnrollment(mapEnrollments.keySet());
        }
    }
}

@future(callout=true)
public static void createNewEnrollment(set<Id> enrollmentIds) {
    System.debug(loggingLevel.Error, 'enrollmentIds==>' + enrollmentIds);
    list<Enrolment__c> listEnrollments = new list<Enrolment__c>([select Id, Opportunity__r.Account.Name, Opportunity__r.Account.Id, Opportunity__r.Account.PersonEmail, Opportunity__r.Related_Course__r.LMS_Code__c from Enrolment__c where Id in: enrollmentIds]);
    list<Account> listAccountUpdate = new list<Account>();
    for(Enrolment__c e: listEnrollments) {
        /* Step 1: Add the user if they don't already exist */

        //Callout to search for the user via email
        String userSearch = 'url string';
        Http h = new Http();

        HttpRequest req = new HttpRequest();
        req.setEndpoint(userSearch);
        req.setMethod('GET');

        HttpResponse res = h.send(req); // <---- Where "uncommitted work pending" error is thrown
        response = res.getBody();
                System.debug(loggingLevel.Error, '*** response: ' + response);

        // code to parse json and form list of accounts to update if user found


        }
    }
    //Update the account 
    update listAccountUpdate;
}

Test class:

@isTest
private class EnrollmentUtil_Test {

static testMethod void testNewEnrollment(){ 
    RecordType rtSA = [Select Id from RecordType Where Name = 'Student Account'];

    Account acc = new Account();
    acc.FirstName = 'accFirst';
    acc.LastName = 'accLast';
    acc.RecordTypeId = rtSA.Id;
    acc.PersonEmail = 'tester@test.com';
    insert acc;

    RecordType rtCUA = [Select Id from RecordType Where Name = 'College/University Account'];

    Account acc1 = new Account();
    acc1.Name = 'AccName';
    acc1.RecordTypeId = rtCUA.Id;
    insert acc1; 

    Qualification__c qual = new Qualification__c();
    qual.Name = 'Test Qual';
    qual.LMS_Code__c = '1';
    qual.Account__c = acc1.Id;
    insert qual;

    Qualification_Unit__c u = new Qualification_Unit__c();
    u.Qualification__c = qual.Id;
    u.Name = 'UnitName';
    insert u;

    RecordType rtSO = [Select Id from RecordType Where Name = 'Student Opportunity'];
    Opportunity opp = new Opportunity();
    opp.Name = 'Test_Student_Opp';
    opp.AccountId = acc.Id;
    opp.RecordTypeId = rtSO.Id;
    opp.StageName = 'In Progress';
    opp.CloseDate = Date.today();
    opp.Related_Course__c = qual.Id;
    opp.Education_Provider_Account__c = acc1.Id;
    opp.CloseDate = date.today()+30;
    insert opp;   

    map<Id,Enrolment__c> mapId = new map<Id,Enrolment__c>();
    Enrolment__c en = new Enrolment__c();
    en.Opportunity__c = opp.Id;
    en.Account__c = acc.Id;
    en.Start_Date__c = Date.today();
    insert en;
    mapId.put(en.Id,en);

    Test.startTest();
    // This causes a fake response to be generated
    Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());


    // Call the method that invokes a callout
    EnrollmentUtil.createNewEnrollment(mapId.keySet());
    Test.stopTest();

}


public class MockHttpResponseGenerator implements HttpCalloutMock {
    // Implement this interface method
    public HTTPResponse respond(HTTPRequest req) {

        // Create a fake response
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody('{"id":1,"html_url":"www.blah.com"}');
        res.setStatusCode(200);
        return res;
    }
}
}

As I said this was previously passing and no change that would impact this has been made since, except for Salesforce releases. The startTest and stopTest flags look to be placed correctly to separate DML statements from the callout, I can't see any DML statements prior to the callout in the method, and in sandbox I've tried removing the @future tag with no change. Could Winter 16 have broken this?

  • Stranger still, in a stripped-out dev environment where I've removed a bunch of workflows and fields but has this code and relevant metadata unchanged, the test is passing. Even if there are related workflows on the Enrollment object, the startTest and stopTest flags and lack of any DML in the code means they shouldn't have any impact on this right? – Philip Clark Oct 30 '15 at 3:05
  • Do you have any workflow email alerts that are new or running? If so, workflow email alerts cause the mock to break - See this question: salesforce.stackexchange.com/questions/71259/… – Eric Oct 30 '15 at 4:04
  • 1
    is it something the method(CreateNewEnrollment) has any DML statements prior to calling the callout? – Goki Oct 30 '15 at 4:14
6

Since you said in your comment that disabling workflows allows it to pass I am going to assume that some of those workflows are email alerts.

This is a know issue which I first reported back in April and now has a know issue associated with it that is unresolved.

Issue with Email Alerts and Test.setMock - Uncommitted Work Pending

Known Issue:

https://success.salesforce.com/issues_view?id=a1p300000008XHBAA2

And I love the workaround:

Separate DML and Workflow (with email) in different transactions

how exactly do you do that?

What I did, I created a flag on the object that the test methods set and have all the workflows causing the issue evaluate to false if that flag was true. No reason to fire off an email in a test anyway. But that also meant I had to isolate the WFR Email alert from the other Field Updates that were in the same workflow. Not doing that could cause your asserts to fail.

  • Thanks so much for the response Eric, otherwise I would have spent the next several days tearing my hair out and the code apart trying to find a solution to a platform bug. There was indeed a workflow to send email off the same object, and once I disabled it the tests passed. I'll try your suggestion for controlling email send with a flag. – Philip Clark Nov 1 '15 at 22:13
  • @PhilipClark - Your welcome. Glad I could help. It was a tough one to initially nail down when I ran into it.... – Eric Nov 1 '15 at 22:49
  • According to Salesforce this was fixed in Winter '17 release patch. Today I had similar issue which turned out to be caused by process builder that was starting approval process. – neff Jan 25 '17 at 20:27
  • @Eric, Thank you so much for your answer, i finally found the solution. Yes in my production org, i do have some WFR on object(Account and contact). i just deactivate it, and validating the change set in production is successful(before i got error on this). my quesion is, if i activate this WFR again (if client want it) after deployment , will it cause error? – Archita patnaik Jul 18 '17 at 7:06
  • If it is true, how to separete the DML and WF in different transaction through Flag, can you explain it more clearly. – Archita patnaik Jul 18 '17 at 7:08

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.