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.
@future
does very little (if anything). If you need to avoid mixed dml, then you should be usingif(System.runAs(new User(Id = UserInfo.getId())))
to run the problematic dml (generally on setup objects likeUser
).start
implementation (or possibly ininitRequest()
). There are things that aren't DML (at least not explicitly) that can cause this issue such asDatabase.setSavepoint()
,System.enqueueJob()
,System.schedule()
, etc...//this definitely needed to be @future to avoid mixed DML issue
. My point was don't use @future, useSystem.runAs()