[SalesForce] You have uncommitted work pending. Please commit or rollback after [sic] calling out

Below class gives error "You have uncommitted work pending. Please commit or rollback after calling out".

//Call out from batch

 public void execute(Database.BatchableContext bc, List<Opportunity> scope) {       

        for(Opportunity o : scope){
            Http http = new Http();
            HttpRequest req = new HttpRequest();
            HttpResponse res = new HttpResponse();
            req.setEndpoint('https://posttestserver.com/post.php/?dump&dir=sandbox&' +o.name);
            req.setHeader('Content-Type','x-www-form-urlencoded');
            req.setHeader('Content-Length', '0'); 
            system.debug('here5');
            req.setMethod('GET');
            req.setTimeOut(1200);
            res = http.send(req);
            String str = res.toString();
}

//Test Method

@isTest
static void testRecordUpdate(){
    User u = createTestUser('System Administrator', 'Test', 'User');
    List<Opportunity> opp = createOppRecord('Open', 1, u);
    for(Opportunity o : opp){
        o.stageName = 'Closed Won';
    }
    Test.setMock(HttpCalloutMock.class, new CalloutMock());
    update opp; // this update makes callout
}

Trying to : When agent updates an opportunity, a callout is made and if response is successful, a record should be created in child object

Thanks.

Best Answer

A quick search may have found the answer, but it is really simple:

Wrap the mock in a test.startTest();

  test.startTest();
    Test.setMock(HttpCalloutMock.class, new CalloutMock());
    update opp; // this update makes callout
  test.stoptest();

the reason is just like in a non-test environment. You cannot make a callout after doing any dml. Since you are creating a User, you cannot do a callout after in the same transaction. the test.StartTest() provides a new context for the callout to be run in as well as a new set of governor limits....

NOTE

Not sure why you have a batch code there, but if you are executing a batch from a trigger (potentially a problem) you will have to:

Call the start, execute, finish methods separately as it is a know issues that you will get this error regardless of the test.startTest if the callout is from a batch

If that is the case, unfortunatly you will have to wrap that part of your trigger that calls the batch in a :

if(!test.isRunningTest())

and change your test method to:

test.startTest();
        Test.setMock(HttpCalloutMock.class, new CalloutMock());
        update opp; // this update makes callout
        //call your start method
        [batch].execute(null,New sObject[]{opp});
       //call your finish method
      test.stoptest();

You can also just wrap the callout in the batch to prevent it from running during a test except when a static property is set. that way your trigger is unaltered and the batch / test controls when the callout is made during a test method

i.e in the batch.

public static testCallout = false;
.....
if( (testCallout && test.isRunningTest()) || !test.isRunningTest())
    //make callout

then set the testCallout = true from your test class

this leaves your trigger unaltered

You can find many topics on this issue by searching for "You have uncommitted work pending batch test" on this site