4

It seems that System.Formula.recalculateFormulas method triggers unexpected behaviour during apex unit tests. Has anyone faced this issue or has any explanation why this is happening? Logs do not show any DML operations, but if I use Formula.recalculateFormulas right before a callout, it will error in tests with System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out. But in the code, it works perfectly well. Code example below:

public class CalloutsForTest {

    final Account account;

    CalloutsForTest(Id accountId) {
        this.account = [SELECT Id FROM Account WHERE Id = :accountId];
    }

    public static void doStatelessCallout(Id accountId){
        CalloutsForTest calloutObject = new CalloutsForTest(accountId);
        calloutObject.doStatefulCallout();
    }

    public void doStatefulCallout() {
        //If recalculateFormulas method (17-21 lines) is recalculated, then test does not fail
        {
            System.Formula.recalculateFormulas(new List<Account>{
                    account
            });
        }
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setMethod('GET');
        request.setEndpoint('https://www.google.com/');
        HttpResponse response;
        response = http.send(request);
        if (response.getStatusCode() != 200) {
            throw new CalloutException(response.getBody());
        }
    }
}
@IsTest
private class CalloutTest {
    @IsTest
    static void test() {
        //If we remove test data preparation DML formula recalculation method do not break unit test
        Id accountId = Database.insert(new Account(Name = 'Test inc.')).getId();
        Test.startTest();
        Test.setMock(HttpCalloutMock.class, new HttpMock());
        CalloutsForTest.doStatelessCallout(accountId);
        Test.stopTest();
        System.assert(true);
    }

    public class HttpMock implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest request) {
            HttpResponse response = new HttpResponse();
            response.setStatusCode(200);
            return response;
        }
    }
}

Quick summary to reproduce:

  1. Create custom object with a formula field dependant on two any other fields
  2. Write a method you will test that changes one of the fields that affect the formula field. Right after you modify the sObject field, call Formulas.recalculateFormula to obtain the value updated in the formula (no DML should happen here, at least it doesn't show in the logs).
  3. Right after the Formula.recalculateFormulas, do a callout anywhere. Sample scenario would be to use the updated value from the formula to charge a credit card. In theory up to here, you should have not made any DMLs.
  4. In your test's setup, store an initial object for testing.
  5. In the test, call your method to change the field's value.
  6. Run the test. You will see the DML callout error, even though there is no DML ops performed.
  7. Remove the Formula.recalculateFormulas and the DML callout error disappears. Again, this only happens when running tests and not the prod execution itself
2
  • Please rewrite edit the OP to just show the example where recalculateFormulas generates DML. The Community will need something it can reproduce
    – cropredy
    Commented Oct 12, 2022 at 18:33
  • The code does not show DML statement in debug logs, but test crashes because of the error: "You have uncommitted work pending. Please commit or rollback before calling out.". This error makes me believe that some kind of unexpected behaviour occurs. Commented Oct 22, 2022 at 12:50

2 Answers 2

3

I reported this issue to Salesforce support, and they created a bug ticket:

Known Issues: In test classes, Formula.recalculateFormulas() is causing error "System.CalloutException: You have uncommitted work pending. Please commit or rollback

It is reproducible by running the test in this code, no additional steps necessary:

@IsTest
private class CalloutTest {
    @TestSetup
    static void setupData() {
        Account acct = new Account(Name='acct');
        insert acct;
    }
    
    @IsTest
    static void test() {
        Test.startTest();
        Test.setMock(HttpCalloutMock.class, new HttpMock());
        
        Account acct = [SELECT Id FROM Account LIMIT 1];
        System.Formula.recalculateFormulas(new List<Account>{acct});
        
        // This will result in: System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
        doCallout();
        
        Test.stopTest();
        System.assert(true);
    }

    static void doCallout() {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setMethod('GET');
        request.setEndpoint('https://www.google.com/');
        HttpResponse response;
        response = http.send(request);
        if (response.getStatusCode() != 200) {
            throw new CalloutException(response.getBody());
        }
    }
    
    public class HttpMock implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest request) {
            HttpResponse response = new HttpResponse();
            response.setStatusCode(200);
            return response;
        }
    }
}
1

I am having the exact same issue. Logs do not show any DML operations, but if I use Formula.recalculateFormulas right before a callout, it will error in tests with System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out. But in the code, it works perfectly well.

Quick summary to reproduce:

  1. Create custom object with a formula field dependant on two any other fields
  2. Write a method you will test that changes one of the fields that affect the formula field. Right after you modify the sObject field, call Formulas.recalculateFormula to obtain the value updated in the formula (no DML should happen here, at least it doesn't show in the logs).
  3. Right after the Formula.recalculateFormulas, do a callout anywhere. Sample scenario would be to use the updated value from the formula to charge a credit card. In theory up to here, you should have not made any DMLs.
  4. In your test's setup, store an initial object for testing.
  5. In the test, call your method to change the field's value.
  6. Run the test. You will see the DML callout error, even though there is no DML ops performed.
  7. Remove the Formula.recalculateFormulas and the DML callout error disappears. Again, this only happens when running tests and not the prod execution itself
1
  • I don't want to remove this because it's useful information, but it doesn't belong here either. Could put it as an extension to the original question via an edit if you're ok with that?
    – Matt Lacey
    Commented Nov 1, 2022 at 11:11

You must log in to answer this question.

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