[SalesForce] Test Batch that call HttpCallOut method using MultiStaticResourceCalloutMock

I have read several document regarding this ,including Testing webservice call out but still I cannot find the solution for my issue.

I am creating test method to test batch class that contain httpcallout method.When I run the test, the result is pass but when I went through the debug log, the code that called callout method such if(statusCode==200) is not covered and got exception like below:

System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out

Here the batch class

global  class MyBatch implements   Database.Batchable<sObject>, Schedulable, Database.Stateful,Database.AllowsCallouts {      

String query='Select Id,Name,Status__c,UserId from Transaction__c where Status__c='\'Pending\'';
Map<String, MyObject> mapMyObject  = new Map<String, MyObject>();

public MyBatch(){
         //contructor contain initialization
       }

        global void execute(SchedulableContext sc) {}

        global Database.QueryLocator start(Database.BatchableContext BC){
            return Database.getQueryLocator(query);
         }

        global void execute(Database.BatchableContext BC, List<Transaction__c> transactions) {
                  for(Transaction__c t:transactions){
                       mapMyObject.put(t.UserId,new MyObject(Name='Bla bla');
                  }
                  system.debug('mapMyObject size '+mapMyObject.size());

                 for (MyObject o : mapMyObject.Values()){
                   //contain logic and call out
                   MyCallOut.getUserByEmail(email)
                   if(statusCode==200){
                      system.debug('Good job!')
                    }
                  //the rest of logic and call cout
                 }
          }
    }

Here the class that contain HttpCallout method that called by my batch class

global class MyCallOut {
     public static string getUserByEmail(string email){
          String returnStr='';
   try{

    String baseUrl = externalURL+'/2.0/admin/users/'+email;
    system.debug('@getUserByEmail  '+ baseUrl );

   HTTPRequest request = new HTTPRequest();              
    request.setEndpoint(baseUrl);
    String authorizationHeader = 'WRAP access_token=' + token;

    request.setHeader('Authorization', authorizationHeader);
    request.setHeader('Content-Type', 'application/xml');
    request.setMethod('GET');
    request.setTimeout(120000);

    HTTP http = new HTTP();
    HTTPResponse response;
            response =  http.send(request);    
            if(response.getStatusCode()>=400){
               returnStr=response.getStatusCode() +' : '+ response.getStatus();
               errorCode=response.getStatusCode();
               errorMessage=returnStr;
             }

            else {
               errorCode=response.getStatusCode();
               returnStr=response.getBody();
            }               

    system.debug('@returnStr  '+ returnStr);
   }

catch(Exception e){

       errorCode=700;
       errorMessage=e.getMessage();
       errorMessage+= ' ::: inside getUserByEmail(string email)';

}

    return returnStr;
     }

    public static string populateProfile(string str{
             //HttpRequest bla bla
    }

   //more httpcall method    
}

Here is the test method.I am using MultiStaticResourceCalloutMock,also please note that I already put Test.startTest() and Test.stopTest() properly as stated in reference link.

 @isTest
public class MyBatchTest {
static{

URLConfig__c setting = new URLConfig__c();
setting.Name = 'Url';
setting.Value__c = 'https://api.bla.com';
insert setting;

URLConfig__c setting1 = new URLConfig__c();
setting1.Name = 'Key';
setting1.Value__c = 'ABC1234323';
insert setting1;

Id folderId=[Select Id from Folder Where Name='Token'].Id;
Blob bToken=Blob.valueOf('JYYTGHGUTY6567GYT8765gXDWSRET');
Document doc1 =new Document(Name='Token',Body =bToken,ContentType='text/plain',FolderId=folderId);
insert doc1;
List<Document> doc =[SELECT Id,Body,Name FROM Document WHERE  Name='Token'];

System.assertEquals(1, doc.size());

 User newUser=new User(alias = 'newuser', email='newuser@domain.com', 
               Department='HR Management', username='newuser@domain.com');
insert newUser;

 System.AssertEquals(
           database.countquery('SELECT COUNT()'
              +' FROM User  where Id  =  \''+newUser.Id+'\''), 
           1);

      List<Transaction__c> trans = [SELECT Id,Name,UserId__c FROM Transaction__c where UserId__c =: newUser.Id];

      system.debug('@test '+trans.size());
 }
static testMethod  void testBatch(){


    MultiStaticResourceCalloutMock multimock = new MultiStaticResourceCalloutMock();
    multimock.setStaticResource('https://api.bla.com/2.0/admin/users/newuser@domain.com', 'UserProfile_Test');
    multimock.setStatusCode(200);
    multimock.setHeader('Content-Type', 'application/json');

    Test.startTest();
     Test.setMock(HttpCalloutMock.class, multimock);

      MyBatch c = new MyBatch();
      c.query='SELECT Id,Name,UserId__c,Status__c '+
                                       +' FROM Transaction__c WHERE Status__c IN (\'Pending\') LIMIT 1';

     Database.executeBatch(c,1);
     Test.stopTest();

}
}

Thanks for your idea.

Best Answer

I think your issue is a known limitation - see my answer here from a while back. I have never found a combination of mock services and test.start/stop that lets you test a batch that contains a callout. I think your best bet is to move the callout to a separate class that you can test independently, and then have the batch check for a running test before calling it

Related Topic