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.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:

private class BatchCalloutTest {

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

    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());

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

        Database.executeBatch(new BatchCallout());

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

        Database.executeBatch(new BatchCallout());
  • 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, 2019 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, 2019 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, 2019 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, 2019 at 16:53

1 Answer 1


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   
Database.executeBatch(new BatchCallout());
  • 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, 2019 at 16:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .