[SalesForce] How to test Post Callout with web token’s access? HttpCalloutMock

My POST Method (data for web token – is my custom object's fields):

public class Token{
public String accessToken{get;set;}    
}

public static String accessTokenBody(){  //our web token (data is in fields)
        Settings__c settings = [SELECT ConsumerKey__c, ClientSecret__c, Username__c, Password__c, SecurityToken__c
                                FROM Settings__c
                                WHERE Name = 'OurSettings'];  
        String consumerKey = settings.ConsumerKey__c;
        String consumerSecret = settings.ClientSecret__c;
        String username = settings.Username__c;
        String password = settings.Password__c + settings.SecurityToken__c;
        String request = 'grant_type=password&client_id=' + consumerKey +'&client_secret=' + consumerSecret +
                         '&username=' + username + '&password='+password;
        return request;
    }

public static String GenerateJSON(Type__c t){
//I will send and post it like a record in another org:
    Map<String, String> fieldMap = new Map<String, String>{
                   'Name' => t.Name,
                   'Desc__c' => t.Type_Description__c};    
    String serialized = JSON.serialize(fieldMap);         
    return serialized;
 }

public static HttpRequest httpRequest(String service){
    String requestBody = accessTokenBody();
    HttpRequest ourRequest = new HttpRequest();
    ourRequest.setBody(requestBody);
    ourRequest.setMethod(service);
    ourRequest.setEndpoint('https://p21.lightning.force.com/services/oauth2/token');
    return ourRequest;
}

public static HttpRequest finalHttpRequest(String token, String method, String endpointUrl){
    HttpRequest finalRequest = new HttpRequest();
    finalRequest.setHeader('Authorization','Bearer ' + token);
    finalRequest.setHeader('Content-Type','application/json');
    finalRequest.setHeader('accept','application/json');
    finalRequest.setMethod(method);
    finalRequest.setEndpoint('https://p21.lightning.force.com/services/oauth2/token' + endpointUrl);
    return finalRequest;
}

public static HttpResponse postCallout(String positionId) {
    Http ourHttp = new Http();
    HttpRequest request = httpRequest('POST');
    //Error is here: 
    HttpResponse response = ourHttp.send(request);      
    Token objAuthenticationInfo = (Token)JSON.deserialize(response.getbody(), Token.class);

    if(objAuthenticationInfo.ACCESS_TOKEN != null){
        Type__c typ = [SELECT Id, Name FROM Type__c WHERE Id =: TypeID];
        HttpRequest finalRequest = finalHttpRequest(objAuthenticationInfo.ACCESS_TOKEN, 'POST', '');
        finalRequest.setBody(GenerateJSON(typ));
        HttpResponse finalResponse = ourHttp.send(finalRequest);
        if(finalResponse.getStatusCode() == 200) {
            System.debug('CREATED:  ' + finalResponse.getBody());
            return finalResponse;
        }else {
            return null;
        }
    }
    return null;
}

Mock:

@isTest
global class AnimalsHttpCalloutMock implements HttpCalloutMock {
    global HTTPResponse respond(HTTPRequest request) {
        HttpResponse response = new HttpResponse();
        response.setHeader('Content-Type', 'application/json');
        response.setBody('{"Name":"ttt"}');
        response.setStatusCode(200);
        return response; 
    }
}

I get an error System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out. I try to make the same web token in my test class, but I think it can't see it. Coverage is just on my getAccess(), httpRequest() and first 4 rows of postCallout():

@isTest
private class CalloutTest {


@isTest
static void testPostCallout() {      
    Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());

    public class Token{
        public String token{get;set;}    
    }

    Settings__c settings = new Settings__c(Name = 'OurSettings',
                                           ConsumerKey__c = '3Mv78JilhgTvnlkjlkjydGLbGpb8xcTB.2lhtT16LdER9QVx_AmoaOb6gN_3OEtHPlvBDzBYWieC189Cp',
                                           ClientSecret__c = '3453453453419024129',
                                           Username__c = 'mail@mail.com',
                                           SecurityToken__c = '6qdfgcdY6ndyURd32J7Ls2VN',
                                           Password__c = 'mypassword1');
    insert settings;
    String consumerKey = settings.ConsumerKey__c;
    String consumerSecret = settings.ClientSecret__c;
    String username = settings.Username__c;
    String password = settings.Password__c + settings.SecurityToken__c;
    String request = 'grant_type=password&client_id=' + consumerKey +'&client_secret=' + consumerSecret +
                     '&username=' + username + '&password='+password;

    Http ourHttp = new Http();
    String requestBody = CalloutClass.accessTokenBody();
    System.debug('request: ' + request);
    System.debug('request2: ' + requestBody);
    HttpRequest ourRequest = new HttpRequest();
    ourRequest.setBody(request);
    ourRequest.setMethod('POST');
    ourRequest.setEndpoint('https://p21.salesforce.com/services/oauth2/token');
    Type__c typ = new Type__c(Name = 'ttt');
    insert typ;
    //System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
    HttpResponse response = CalloutClass.postCallout(typ.Id);
    String contentType = response.getHeader('Content-Type');
    System.assert(contentType == 'application/json');
    String actualValue = response.getBody();
    System.debug(response.getBody());
    String expectedValue = '{"Name":"ttt"}';
    System.assertEquals(actualValue, expectedValue);
    System.assertEquals(200, response.getStatusCode());
}    
}

Best Answer

You are getting System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out error because A Web Service Callout may not occur after a DML statement within the same transaction.

Solution : To achieve the required action, the transaction must be separated into two parts so that the DML transaction is completed before the Web Service Callout occurs. So use Test.startTest and Test.stopTest to separate context.

 Test.startTest(); 
 Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());
 HttpResponse response = CalloutClass.postCallout(typ.Id); 
 Test.stopTest(); 
Related Topic