5

This question already has an answer here:

I noticed yesterday that one of my new test cases was failing, and determined from the logs that I was getting the beloved System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out error. The exception was actually caught and only discovered after I added an assertion for an edge case - remember those System.assert() calls, kids!

The code runs fine in practice and makes the callout and subsequent future method call as expected. This appears to be limited to my test method. The strange thing is...I'm only getting the error in one of the two test methods that executes that code. The two test cases are more or less identical, and follow a pattern similar to this:

Test.startTest();
Test.setMock(HttpCalloutMock.class, new ThirdPartyAPICalloutMock());

RestContext.request = newRestRequest('/v1/someendpoint', 'POST', new Map<String, Object>({'param'=>'blah'}));
RestContext.response = new RestResponse();
ExposedRestAPI.doPOST();

Test.stopTest();    // this is where the Queueable class would be executed

// bunch of assertions to validate the results of both doPOST and the Queueable class

The ExposedRestAPI.doPOST() method is rather complex (although I doubt its implementation is the problem here), but for the purposes of this question it should suffice to say that it queues up the Queueable class in question:

System.enqueueJob(new StrangeQueueable(new StrangeQueueable.Config(rec.Id, false)));

The Queueable class has a structure similar to the following code. It makes a query and then calls another class to make a callout:

public without sharing class StrangeQueueable implements Queueable, Database.AllowsCallouts {

    public Config conf;

    public StrangeQueueable(Config conf) {
        this.conf = conf;
    }

    public void execute(QueueableContext context) {
        System.debug('StrangeQueueable logic');

        Stopwatch.start();
        API_Log__c log = new API_Log__c(Request_Body__c=JSON.serializePretty(conf));

        Some_Object__c rec;
        try {

            rec = [select Id, Custom_Field__c, Custom_Field_2__c, Etc__c from Some_Object__c where Id = :conf.recId];

            ThirdPartyObject cr = ThirdPartyAPI.doSomeStuff(rec);

        } catch ( Exception e ) {
            System.debug(LoggingLevel.ERROR, e);
            System.debug(LoggingLevel.ERROR, e.getStackTraceString());
            log.Has_Errors__c = true;
            log.Errors__c = e.getMessage() + '\n' + e.getStackTraceString();
        } finally {
            log.Resource_Path__c = 'StrangeQueueable.execute()';
            log.Request_URI__c = 'StrangeQueueable.execute('+conf.recId+')';
            log.Request_Method__c = 'ASYNC';
            log.Request_Duration__c = Stopwatch.getElapsedTime();
            log.Callout_Time__c = Stopwatch.getCalloutTime();
            log.Some_Object__c = (rec==null ? rec : rec.Id);
            Database.insert(log, false);
        }

        if ( conf.someFlag ) {
            // apparently we can't call another Queueable from a Queueable that makes a callout! this is madness!
            //System.enqueueJob(new AnotherQueueable(parama1, param2));
            callAnotherFutureMethod(param1, param2);
        }

    }

    @future(callout=true)
    public static void callAnotherFutureMethod(Id appId, Id clientId) {
        System.debug('callAnotherFutureMethod logic');

    }

    public class Config {
        Id recId;
        Boolean someFlag;
        public Config(Id recId, Boolean someFlag) {
            this.recId = recId;
            this.someFlag = someFlag;
        }
    }

}

The ThirdPartyAPI class makes a callout and I've confirmed it makes no DML statements prior to calling out (wouldn't be a very effective callout method if it did!)

Now the kicker...in the one test method, my Queueable fails with Uncommited Work Pending. The other not only runs to completion, but also executes the callAnotherFutureMethod future method (but not in any context where we can test the results...it seems that it runs after the test method has finished...I suppose it's just another oddity of testing async Apex).

Here's the failure:

USER_DEBUG|[24]|DEBUG|end of REST context
USER_DEBUG|[24]|DEBUG|queries        :  10 of 100 (10.00%)
USER_DEBUG|[33]|DEBUG|query rows     :  5 of 50000 (0.01%)
USER_DEBUG|[42]|DEBUG|DML operations :  9 of 150 (6.00%)
USER_DEBUG|[51]|DEBUG|DML rows       :  9 of 10000 (0.09%)
USER_DEBUG|[60]|DEBUG|callouts       :  0 of 100 (0.00%)
USER_DEBUG|[11]|DEBUG|StrangeQueueable logic          // Test.stopTest() would have been called here
SOQL_EXECUTE_BEGIN|[19]|Aggregations:0|SELECT Id, Some_other_fields__c FROM Some_Object__c WHERE Id = :tmpVar1
SOQL_EXECUTE_END|[19]|Rows:1
USER_DEBUG|[136]|DEBUG|generateRequestXML: <REQUEST_GROUP> ... </REQUEST_GROUP>
USER_DEBUG|[24]|DEBUG|some debug info to show no DML operations...
USER_DEBUG|[24]|DEBUG|queries        :  2 of 200 (1.00%)
USER_DEBUG|[33]|DEBUG|query rows     :  3 of 50000 (0.01%)
USER_DEBUG|[42]|DEBUG|DML operations :  0 of 150 (0.00%)        // no DML operations before callout
USER_DEBUG|[51]|DEBUG|DML rows       :  0 of 10000 (0.00%)
USER_DEBUG|[60]|DEBUG|callouts       :  0 of 100 (0.00%)
USER_DEBUG|[70]|DEBUG|ThirdPartyAPI request: System.HttpRequest[Endpoint=https://demo.somethirdparty.com/blah.aspx, Method=POST]
EXCEPTION_THROWN|[74]|System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
USER_DEBUG|[41]|ERROR|System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
USER_DEBUG|[42]|ERROR|Class.ThirdPartyAPI.sendRequest: line 74, column 1

And here's it running to completion:

USER_DEBUG|[24]|DEBUG|end of REST context
USER_DEBUG|[24]|DEBUG|queries        :  6 of 100 (6.00%)
USER_DEBUG|[33]|DEBUG|query rows     :  1 of 50000 (0.00%)
USER_DEBUG|[42]|DEBUG|DML operations :  7 of 150 (4.67%)
USER_DEBUG|[51]|DEBUG|DML rows       :  7 of 10000 (0.07%)
USER_DEBUG|[60]|DEBUG|callouts       :  0 of 100 (0.00%)
USER_DEBUG|[11]|DEBUG|StrangeQueueable logic          // Test.stopTest() would have been called here
SOQL_EXECUTE_BEGIN|[19]|Aggregations:0|SELECT Id, Some_other_fields__c FROM Some_Object__c WHERE Id = :tmpVar1
SOQL_EXECUTE_END|[19]|Rows:1
USER_DEBUG|[136]|DEBUG|generateRequestXML: <REQUEST_GROUP> ... </REQUEST_GROUP>
USER_DEBUG|[24]|DEBUG|some debug info to show no DML operations...
USER_DEBUG|[24]|DEBUG|queries        :  2 of 200 (1.00%)
USER_DEBUG|[33]|DEBUG|query rows     :  3 of 50000 (0.01%)
USER_DEBUG|[42]|DEBUG|DML operations :  0 of 150 (0.00%)        // no DML operations before callout
USER_DEBUG|[51]|DEBUG|DML rows       :  0 of 10000 (0.00%)
USER_DEBUG|[60]|DEBUG|callouts       :  0 of 100 (0.00%)
USER_DEBUG|[70]|DEBUG|ThirdPartyAPI request: System.HttpRequest[Endpoint=https://demo.somethirdparty.com/blah.aspx, Method=POST]
CALLOUT_REQUEST|[74]|System.HttpRequest[Endpoint=https://demo.somethirdparty.com/blah.aspx, Method=POST]
CALLOUT_RESPONSE|[74]|System.HttpResponse[Status=null, StatusCode=200]        // mocked callout went through as expected
USER_DEBUG|[78]|DEBUG|ThirdPartyAPI response: <RESPONSE_GROUP> ... </RESPONSE_GROUP>
SOQL_EXECUTE_BEGIN|[70]|Aggregations:0|SELECT Id, Blah__c FROM Some_Object__c WHERE Id = :tmpVar1
// a lot more log info from what happens next
// and later...the future method runs AFTER the test case has finished? weird.
CODE_UNIT_FINISHED|StrangeQueueable_Test.testInbound
CODE_UNIT_STARTED|[EXTERNAL]|01p630000005jdc|StrangeQueueable.callAnotherFutureMethod
USER_DEBUG|[65]|DEBUG|callAnotherFutureMethod logic

Anybody seen anything like this? For reference, all classes involved here are on API v36. I've also confirmed that I'm not calling Database.setSavepoint() anywhere in either test method.

marked as duplicate by Adrian Larson apex Aug 8 '18 at 15:43

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 2
    +1 for beloved and shout out for system.assert The odd (weird) debug logs I have noticed as well when testing async apex - I chose to ignore. – cropredy Aug 9 '16 at 16:12
  • 1
    Typo? Your syntax shouldn't compile. newRestRequest('...', '...', new Map<String, Object>({'...'=>'...')); should be newRestRequest('...', '...', new Map<String, Object>({'...'=>'...'});. – Adrian Larson Aug 9 '16 at 16:42
  • Thanks @AdrianLarson, that was just a typo when putting this question together. Originally I was just passing a map variable but didn't want to go into too much detail that wasn't relevant to the error, so I figured I'd remove it. – JCD Aug 9 '16 at 17:10
  • Found a possible explaination here: salesforce.stackexchange.com/questions/203070/… Looks like queueing the job counts as a DML – Max Stackhouse Aug 8 '18 at 13:27
1

Found a possible explaination here: Uncommitted work pending in unit test with trigger and queueable callout

Looks like queueing the job counts as a DML

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