0

I think this will be a easy fix, I am sure it is something I am just overlooking at the end of the day.

I created a mock class to test my callouts. It is called SingleRequestMockClass.cls, I got the structure from here: https://developer.salesforce.com/blogs/developer-relations/2013/03/testing-apex-callouts-using-httpcalloutmock.html .

When I pass it a 401 it for some reason doesn't register it. Shouldn't it just return the status code that I am setting it to? Can anyone see what I am doing wrong here?

public static String handleResponse(HttpResponse res){
    system.debug('got into handle response:: ' + res);
    String errorData = '';
    String errorString;
    Boolean resErrors;

    if (res.getStatusCode() == 200) {
        Error error = new Error();
        resErrors = false;
        System.JSONParser parser = JSON.createParser(res.getBody());

        while (parser.nextToken() != null) {
            if (parser.getCurrentToken() == JSONToken.START_OBJECT && parser.getCurrentName() == 'kind') {
                resErrors = false;//all requests succeeded
            }
            if (parser.getCurrentToken() == JSONToken.START_OBJECT && parser.getCurrentName() == 'error') { //errors
                resErrors = true;
                error = (Error)parser.readValueAs(DoubleClick_Webservice.Error.class);
            }
        }
        if(resErrors) { //error handling
            errorString += '\n\nNew Error --- \nReason: ' + errorData;
        }
        else { //success handling
            errorString = 'success';
        }
    }
    else { //HTTP Error
        //sending email to notify for authentication errors
        if (res.getStatusCode() == 401) {
            sendAuthErrorEmail(res);
        }
        errorString = 'StatusCode: ' +res.getStatusCode() + ' :: ' + res.getStatus();
        Error error = new Error();
        System.JSONParser parser = JSON.createParser(res.getBody());

        while (parser.nextToken() != null) { //errors
            if (parser.getCurrentToken() == JSONToken.START_OBJECT && parser.getCurrentName() == 'error') {
                error = (Error)parser.readValueAs(DCS_SingleGoogleAppAuth_Webservice.Error.class);
            }
        }
        List<Errors> errorsList = error.errors;
        for (Errors e : errorsList) {
            errorString += '\n\nNew Error --- \nReason: ' + e.reason + '\nMessage: ' + e.message;
        }
    }
    return errorString;
}

TEST CLASS

static testmethod void testParseErrorCallout() {
    Test.startTest();
    SingleRequestMockClass fakeResponse = new SingleRequestMockClass(401, 'Complete', '{"access_token" : "ya22.123Ceuppb7J9123454654879878655dJTZlw","expires_in" : 3600,"token_type" : "Bearer"}', null);
    Test.setMock(HttpCalloutMock.class, fakeResponse);
    DoubleClickRefreshToken__c dcrt = new DoubleClickRefreshToken__c(Refresh_Token__c = '123', Key__c = '321', Secret__c = '456');
    insert dcrt;
    Lead l = new Lead(lastname='TestLead', Company = 'TestCompany', GCLID_ID__C = '123456', GCLSRC_ID__C = '32154');
    insert l;
    DoubleClick_Request__c dcr = new DoubleClick_Request__c(Related_Lead__c = l.id, Request_Failed__c = FALSE, Conversion_Status__c = 'New', Segmentation_Name__c = 'Test 1', Testmode__c = FALSE);
    insert dcr;
    Test.stopTest();
}

Callout:

public static HttpResponse retrieveAccessToken(DoubleClickRefreshToken__c doubleClickRefreshToken) {
    HttpRequest req = new HttpRequest();
    req.setMethod('POST');
    req.setEndpoint('https://accounts.google.com/o/oauth2/token');
    req.setHeader('content-type', 'application/x-www-form-urlencoded');
    String messagebody = 'client_id=' + doubleClickRefreshToken.Key__c + '&client_secret=' + doubleClickRefreshToken.Secret__c + '&grant_type=refresh_token&refresh_token=' + doubleClickRefreshToken.Refresh_Token__c;
    system.debug('this is the body ----------------->' + messagebody);

    req.setHeader('Content-Length', String.valueOf(messagebody.length()));
    req.setBody(messagebody);
    req.setTimeout(120000);
    Http h = new Http();
    HttpResponse res;
    try {
        res = h.send(req);
        system.debug('res in accesstoken2:: ' + res);
    }
    catch(Exception e){
        system.debug('exception e:: ' + e);
    }
    return res;
}

It is getting caught on this line: if (parser.getCurrentToken() == JSONToken.START_OBJECT && parser.getCurrentName() == 'error') but to my understanding, if the authorization token is incorrect (which is it), the there should be a string generated with the error code in it. Do I have to pass back this error code? Like this:

SingleRequestMockClass fakeResponse = new SingleRequestMockClass(401, 'Complete', '{"error": {"errors": [{"domain": "global","reason": "authError","message": "Invalid Credentials","locationType": "header","location": "Authorization"}],"code": 401,"message": "Invalid Credentials"}}', null);

Mock Class:

@isTest
public class SingleRequestMockClass implements HttpCalloutMock {
    protected Integer code;
    protected String status;
    protected String bodyAsString;
    protected Blob bodyAsBlob;
    protected Map<String, String> responseHeaders;

    public SingleRequestMockClass(Integer code, String status, String body, Map<String, String> responseHeaders) {
        this.code = code;
        this.status = status;
        this.bodyAsString = body;
        this.bodyAsBlob = null;
        this.responseHeaders = responseHeaders;
    }

    public SingleRequestMockClass(Integer code, String status, Blob body, Map<String, String> responseHeaders) {
        this.code = code;
        this.status = status;
        this.bodyAsBlob = body;
        this.bodyAsString = null;
        this.responseHeaders = responseHeaders;
    }

    public HTTPResponse respond(HTTPRequest req) {
        HttpResponse resp = new HttpResponse();
        resp.setStatusCode(code);
        resp.setStatus(status);
        if (bodyAsBlob != null) {
            resp.setBodyAsBlob(bodyAsBlob);
        } else {
            resp.setBody(bodyAsString);
        }

        if (responseHeaders != null) {
             for (String key : responseHeaders.keySet()) {
                resp.setHeader(key, responseHeaders.get(key));
             }
        }
        return resp;
    }
}
5
  • 4
    Add those assertions! Verifying that your code works as you expect it to work is the most important part of unit testing. It’s also one of the things that Force.com developers commonly neglect. Unit tests that do not verify the results of the code aren’t true unit tests. They are commonly referred to as smoke tests, which aren’t nearly as effective or informative as true unit tests. - How to Write Good Unit Tests
    – Adrian Larson
    Apr 11, 2018 at 22:18
  • you'll need to post the SingleRequestMockClass and edit out using edit the OP parts that are irrelevant to your issue
    – cropredy
    Apr 12, 2018 at 0:47
  • In your else block (not 200 code), you continue on after checking if (res.getStatusCode() == 401) { sendAuthErrorEmail(res); }. Seems like maybe that edge case should be an else if rather than nested within the else.
    – Adrian Larson
    Apr 12, 2018 at 14:32
  • It seems like you're expecting your mock class to return a Body value different from the one that you supply, that is transformed to include an error value. That won't happen unless you code it to happen.
    – David Reed
    Apr 12, 2018 at 14:36
  • @DavidReed thank you, I was mixing up what it should be returning vs what I should be sending it. I also realized that rather then doing two unit tests for each callout (there are two) I created a MultiRequestMock class to hit both in the test. This worked well, I will post the answer
    – Olivia
    Apr 12, 2018 at 19:08

1 Answer 1

0

My issue was I had two callouts that were reliant on one another. I created a Mock Class that would handle two callouts:

Mock:

public class MultiRequestMock implements HttpCalloutMock {
Map<String, HttpCalloutMock> requests;

public MultiRequestMock(Map<String, HttpCalloutMock> requests) {
    this.requests = requests;
}

public HTTPResponse respond(HTTPRequest req) {
    HttpCalloutMock mock = requests.get(req.getEndpoint());
    if (mock != null) {
        return mock.respond(req);
    } else {
        throw new MultiRequestMockException('HTTP callout not supported for test methods');
    }
}

public void addRequestMock(String url, HttpCalloutMock mock) {
    requests.put(url, mock);
}

public class MultiRequestMockException extends Exception {}

}

Test:

static testmethod void testParse401Callout() {
    DoubleClickRefreshToken__c dcrt = new DoubleClickRefreshToken__c(Refresh_Token__c = '123', Key__c = '321', Secret__c = '456');
    insert dcrt;
    Lead l = new Lead(lastname='TestLead', Company = 'TestCompany', GCLID_ID__C = '123456', GCLSRC_ID__C = '32154');
    insert l;
    SingleRequestMockClass fakeAthTokenResp = new SingleRequestMockClass(200,
            'Complete',
            '{"access_token" : "123456_789-101112","expires_in" : 3600,"token_type" : "Bearer"}',
            null);
    SingleRequestMockClass fakeGoogleAPIResp = new SingleRequestMockClass(401,
            'Error',
            '{"error": {"errors": [{"domain": "global","reason": "authError","message": "Invalid Credentials","locationType": "header", "location": "Authorization"}],"code": 401, "message": "Invalid Credentials"}}',
            null);
    Map<String, HttpCalloutMock> endpoint2TestResp = new Map<String,HttpCalloutMock>(); endpoint2TestResp.put('https://accounts.google.com/o/oauth2/token',fakeAthTokenResp); endpoint2TestResp.put('https://www.googleapis.com/doubleclicksearch/v2/conversion',fakeGoogleAPIResp);
    HttpCalloutMock multiCalloutMock = new MultiRequestMock(endpoint2TestResp);
    Test.startTest();
    Test.setMock(HttpCalloutMock.class, multiCalloutMock);
    Test.stopTest();
    DoubleClick_Request__c dcr = [SELECT Error_Message__c FROM DoubleClick_Request__c WHERE Related_Lead__c = :l.Id];
    System.assertEquals(dcr.Error_Message__c, 'error');    
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .