0

I could have sworn this was working 2 months ago and now it seems suddenly broken (there are some slight changes I have tried to make today - making the method calls @future in @testsetup, also adding startTest and stopTest into testSetup - to no avail)

What could I do to try and fix? or even to debug it -> not able to track where and why it is erroring out (I understand about making callouts and ensuring DML after the callout and/or different transaction and/or @future).

If I remove the steps in @testsetup it works but those steps are needed to setup the admin data needed. I am 100% sure it worked fine about 2 months ago and I was getting 92% coverage. It seems to work ok on the QA/UAT server as well - just not in my SB all of a sudden

SyncUserTest.cls

    @IsTest
    private class SyncUserTest {
        @testSetup
        static void setupData(){
           test.startTest(); //trying everything to create a separate txn
           createAdminData();
           createUsers();
           test.stopTest();
    
        }

        @future //doesn't need to be @future but trying everything here to create separate txn
        static void createAdminData(){
            List<Application_Properties__c> custSettings=new List<Application_Properties__c>();
            custSettings.add(new Application_Properties__c(name='DS_API_Enable',value__c='true'));
            ......
            insert CustSettings; //these custom settings are needed to pass parameters to the callout
        }
    
        @future //this definitely needed to be @future to avoid mixed DML issue
        static void createUsers(){
            UserDataFactory userDataFactory = new UserDataFactory(User.SObjectType);
    
            List<User> users = new List<User>();
            .....
            insert users;
        }

    @IsTest
    static void testClose() {
        List<User> listUsers = [select id from User where Isactive = false'];
        test.startTest();
        test.setMock(HTTPCalloutMock.class,new MockHTTPResponseGenerator());
        Id batchId1= callBatches('CLOSE');
        Test.stopTest();
        listUsers = [select id from User where Isactive = false'];
        system.assertEquals(...);
    }

    private static Id callBatches(String theMethod) {
        return Database.executeBatch(new SyncUser(theMethod));
    }

SyncUser.cls

global class SyncUser implements Database.Stateful, Database.Batchable<sObject>, Database.AllowsCallouts,Schedulable{
global Database.QueryLocator start(Database.BatchableContext BC){
        ...
        initRequest()
        ...
}
global void initRequest() {
        HttpRequest req = new HttpRequest();
        req.setMethod('POST');
        String authendpoint = settingsMap.get('DS_API_AuthEndpoint');
        req.setEndPoint(authendpoint);
        req.setHeader('Content-type', 'application/x-www-form-urlencoded');
        ....
        req.setBody(requestBody);
        try{
            Http http = new Http();
            System.debug(Limits.getDMLStatements());
            HTTPResponse res = http.send(req);
        }
        catch (Exception e) {
            loginError= 'Error occurred during initial token management process: \n'+e.getMessage();
            System.debug(loginError);
        }

Debug log:

13:06:29.913 (2053116965)|EXCEPTION_THROWN|[120]|System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
13:06:29.913 (2053368109)|USER_DEBUG|[151]|DEBUG|Error occurred during initial token management process: 
You have uncommitted work pending. Please commit or rollback before calling out

I don't believe I need to move initRequest() to a separate @future method as I am not doing any DML there at all. AFAIK, the only DML I am doing so far, until the point of the error is in the testsetup where I am creating custom settings and test users (I have removed all triggers from UAT).

I Can't move the lines in-between startTest and stopTest in testClose() because I need to mock the callout.

7
  • Making your setup helpers @future does very little (if anything). If you need to avoid mixed dml, then you should be using if(System.runAs(new User(Id = UserInfo.getId()))) to run the problematic dml (generally on setup objects like User).
    – Derek F
    Commented Nov 16, 2020 at 14:05
  • At this point, I think it's likely that the issue lies somewhere in the code that you've omitted in your start implementation (or possibly in initRequest()). There are things that aren't DML (at least not explicitly) that can cause this issue such as Database.setSavepoint(), System.enqueueJob(), System.schedule(), etc...
    – Derek F
    Commented Nov 16, 2020 at 14:15
  • There isn't an issue with mixed DML at the moment
    – MarcLee
    Commented Nov 16, 2020 at 14:16
  • This comment in your test class suggests otherwise //this definitely needed to be @future to avoid mixed DML issue. My point was don't use @future, use System.runAs()
    – Derek F
    Commented Nov 16, 2020 at 14:36
  • Oh I see now what you mean, I thought you meant that to fix the issue I am having NOW to try system.runAs(). Got it.
    – MarcLee
    Commented Nov 16, 2020 at 14:47

1 Answer 1

0

Calling Database.executeBatch method from another actually inserts a record of ApexAsyncJob in the database. so calling Id batchId1= callBatches('CLOSE'); inside startTest and stop test will try executing your batch and make the callout. And thus you will get your result uncommited work pending error.

The solution is to directly call the batch execute in between test.StartTest and Test.stopTest

Test.startTest();
Test.setMock(HttpCalloutMock.class, new CalloutTestMock(caseRecord.Id, status));
Database.executeBatch(new SyncUser('CLOSE'));
Test.stopTest();

Hoping this will solve your issue:

4
  • unfortunately not, I moved it but still got same error. Indeed I think it does have something to do with Batches and callouts. But as I mentioned it seems to be working in QA/UAT server, and also was working previously but suddenly stopped working
    – MarcLee
    Commented Nov 16, 2020 at 16:36
  • please share the mock class also Commented Nov 16, 2020 at 17:33
  • are you sure you have written what you meant to write? I think when you say to call the execute directly you probably mean batchclass.execute() as opposed to Database.executebatch(new batchclass()). The latter will still probably write to ApexAsyncJob
    – MarcLee
    Commented Nov 17, 2020 at 11:40
  • My guess is it should be Database.executebatch(new batchclass()) , When its kept inside a Test.start and stop it becomes synchronous in nature Commented Nov 17, 2020 at 12:25

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