Apologies ahead of time for the lengthy post, but I'm receiving the following error when attempting to run the test method below. The error occurs when the upsertProject
method is called.
System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
Test Method
@isTest static void upsertErrorTest() {
// retrieve project to pass to upsert method
Project__c p = [SELECT Id, Name FROM Project__c LIMIT 1];
system.assertEquals('Test Project', p.Name);
// retrieve replicon project data in apex wrapper
Replicon.Project project = Replicon.getProjectById(p.Id);
// instantiate payload to use in mock http request
String errorPayload = '[insert json payload here]';
Test.startTest();
// instantiate the http service mock
RepliconServiceUpsertMock mock = new RepliconServiceUpsertMock(200, 'Success', errorPayload, null);
Test.setMock(HttpCalloutMock.class, mock);
RepliconService service = new RepliconService();
HttpResponse response = service.upsertProject(project, p.Id);
// assertions here
Test.stopTest();
}
Http Callout Method
public HttpResponse upsertProject(Replicon.Project project, String projectId) {
// serialize the project object for insertion into replicon
String requestBody = project.toString();
// instantiate the request to send to replicon
HttpRequest req = this.getRequest('ImportService1.svc/PutProject4');
req.setBody(requestBody);
// send the request to replicon to create a new project
Http h = new Http();
HttpResponse response = h.send(req);
system.debug('[upsertProject] Response:' + response.getBody());
return response;
}
Test data is created via an @testSetup
method in the test class:
@testSetup static void setupData() {
Account a = new Account();
a.Name = 'Test Account';
insert a;
Opportunity o = new Opportunity();
o.AccountId = a.Id;
o.Name = 'Test Opp';
o.StageName = 'First stage';
o.CloseDate = system.today();
o.Amount = 55000.00;
insert o;
Project__c p = new Project__c();
p.Account__c = a.Id;
p.Opportunity__c = o.Id;
p.Name = 'Test Project';
insert p;
}
Mock Class
@isTest
public class RepliconServiceUpsertMock implements HttpCalloutMock {
protected Integer code;
protected String status;
protected String body;
protected Map<String, String> responseHeaders;
public RepliconServiceUpsertMock(Integer code, String status, String body, Map<String, String> responseHeaders) {
this.code = code;
this.status = status;
this.body = body;
this.responseHeaders = responseHeaders;
}
public HTTPResponse respond(HTTPRequest req) {
HttpResponse res = new HttpResponse();
for(String key : this.responseHeaders.keySet()) {
res.setHeader(key, this.responseHeaders.get(key));
}
res.setBody(this.body);
res.setStatusCode(this.code);
res.setStatus(this.status);
return res;
}
}
The documentation on using Http callouts with Mocks states:
By default, callouts aren’t allowed after DML operations in the same transaction because DML operations result in pending uncommitted work that prevents callouts from executing. Sometimes, you might want to insert test data in your test method using DML before making a callout. To enable this, enclose the portion of your code that performs the callout within Test.startTest and Test.stopTest statements. The Test.startTest statement must appear before the Test.setMock statement. Also, the calls to DML operations must not be part of the Test.startTest/Test.stopTest block.
DML operations that occur after mock callouts are allowed and don’t require any changes in test methods.
My code is definitely following the order of execution described above. There is also no other processes (e.g. future, scheduled apex, etc) being generated from related triggers. So I'm perplexed as to why I'm receiving this error.
Additionally, I have attempted to move the test data creation into the test method itself and do away with the @testSetup
method, but that produced the same results.
Any idea why this is happening?
RepliconServiceUpsertMock
andRepliconService.upsertProject()
look like? Any hidden DML in there? Maybe check the debug log for events immediately before upsertProject is called.Replicon.getProjectById(p.Id)
is doing. Maybe you could just manually create a mockReplicon.Project
for the purpose to exercising the API call.