2

From the top, I do understand how "You have uncommitted work pending" errors happen, but the following is very strange behaviour from Salesforce...

It seems that for one org we have, creating an OpportunityContactRole before running a batch class which performs a callout causes an "uncommitted work pending" error. I've tried reproducing in a sandbox, and it doesn't happen there. The same code works if there is an Account inserted instead of an OCR before starting the batch.

I've tried disabling other automation in the org where this is happening, but still no luck. Although, really, I'd expect that other processes should have no effect anyway as the transactions should be isolated.

I've checked through the debug logs and confirmed that there's only one instance of DML_BEGIN in each, and nothing unexpected firing off.

Any idea what could cause this?

My code follows...

The batch class:

public with sharing class BatchCallout implements Database.Batchable<SObject>, Database.AllowsCallouts {

    public void execute(Database.BatchableContext param1, List<Object> param2) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(Url.getOrgDomainUrl().toExternalForm() + '/services/data/v44.0/limits');
        req.setMethod('GET');
        req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
        new Http().send(req);
    }

    public void finish(Database.BatchableContext param1) {}

    public Iterable<SObject> start(Database.BatchableContext param1) {
        return (Iterable<SObject>)new List<Account>{new Account()};
    }
}

The test class:

@IsTest
private class BatchCalloutTest {

    private class CalloutMock implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest param1) {
            return new HttpResponse();
        }
    }

    @TestSetup
    static void setup() {
        Opportunity testOpportunity = new Opportunity(
                Name = 'Test Opportunity 1',
                CloseDate = Date.today().addDays(30),
                StageName = 'Qualified Enquiry'
        );
        insert testOpportunity;
        Contact testContact = new Contact(
                FirstName = 'First ' + String.valueOf(Datetime.now().getTime()),
                LastName = 'Last ' + String.valueOf(Math.random() * 10000)
        );
        insert testContact;
    }

    static {
        Test.setMock(HttpCalloutMock.class, new CalloutMock());
    }

    @IsTest
    static void withoutOpportunityContactRole() {
        insert new Account(Name = 'Just some DML');

        Test.startTest();
        Database.executeBatch(new BatchCallout());
        Test.stopTest();
    }

    @IsTest
    static void withOpportunityContactRole() {
        insert new OpportunityContactRole(
                OpportunityId = [SELECT Id FROM Opportunity].Id,
                ContactId = [SELECT Id FROM Contact].Id,
                IsPrimary = false
        );

        Test.startTest();
        Database.executeBatch(new BatchCallout());
        Test.stopTest();
    }
}
  • 3
    I know that something similar happens with Queueable apex contained inside of Test.startTest() and Test.stopTest(). Maybe it'd be worth trying to call your execute() method directly (your test shouldn't need to test that Salesforce's Batchable interface works correctly, that's Salesforce's responsibility). – Derek F Dec 17 '19 at 16:48
  • Thanks Derek... That's our fallback position. But it's weird that the OCR changes the behaviour, and I'd rather not have a workaround like that in my test :-( I've stripped off a load of complexity for the example above - in the original code, the batch is triggered by doing a DML on something else. But the code in the question exhibits the same issue in a simpler context – Aidan Dec 17 '19 at 17:03
  • Your experience makes me think of the Salesforce Ohana Slack Channel #whyadminsdrink - I'm with Derek and if you have the inclination, pursue a support case – cropredy Dec 17 '19 at 21:18
  • The fact that OCR just got "promoted" to act like a real object makes me wonder if it's a regression (or at least a new behavior) stemming from that change. – David Reed Dec 18 '19 at 16:53
0

One way I've gotten around things like this in tests is to useSystem.runAs to separate the context.

System.runAs(new User(Id = UserInfo.getUserId())) {
    //insert your OCR here   
}
Test.startTest();
Database.executeBatch(new BatchCallout());
Test.stopTest();
|improve this answer|||||
  • Good idea - sadly doesn't help in this instance :-( Moving everything into @TestSetup is another one I sometimes try, but didn't work here either – Aidan Dec 18 '19 at 16:36

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.