0

I have an after insert trigger on Account which makes an APEX callout to external API to query a value which is then updated on Account object's custom field. The challenge I am facing is that I am not able to get the proper test coverage percentage. Could anyone advise what am I missing or any other better way to write the test class?

Here is how my trigger looks like:

trigger accountBeyondId on Account (after insert) {

    Set<Id> accountIds = new Set<Id>();

    for (Account a : Trigger.new) {
        if(a.Beyond_Unique_ID__c == null) {
            accountIds.add(a.Id);
        }
    }  

    if (accountIds.size() > 0) {
        BeyondApiService.fetchBeyondClientIds(accountIds);
    }
}

Below is my class which has a @future call which internally calls the another private method which makes the external service call and updated the field on Account object.

global class BeyondApiService {

    @future(callout=true)
    public static void fetchBeyondClientIds(set<Id> accountIds) {
            List<Account> accounts = [SELECT Id, Name, Beyond_Unique_ID__c FROM Account WHERE Id = :accountIds];
           for (Account account : accounts) {
               GenerateBeyondClientId(account);
           }
    }

    // Generate BeyondCLientId and update it on Account object.
     public static void GenerateBeyondClientId(Account account) {
        try {
            String authorizationHeader = 'Bearer ' + GetAccessToken();

            string bodyText = '{"dbaName":"'+ account.name +'"}';
            Http http = new Http();
            HttpRequest request = new HttpRequest();
            request.setEndpoint('callout:BIDApiCredetials/api/Client');
            request.setMethod('POST');
            request.setHeader('Content-Type', 'application/json');
            request.setHeader ('Authorization', authorizationHeader);
            request.setBody(''+ bodyText + '');

            HTTPResponse response = http.send (request);
            integer clientId = 0;

            if (response.getStatusCode () == 200) {
                Map <String, Object> jsonParserResult = (Map <String, Object>) SYSTEM.JSON.deserializeUntyped (response.getBody ());
                clientId =  (integer)jsonParserResult.get ('clientId');
                System.Debug ('----------'+ clientId);                    

                account.Beyond_Unique_ID__c = clientId;
                update account;
            }                     
        }
        catch (Exception e) {
            System.Debug ('------Exception-----'+e.getMessage ());
        }        
     }


    //Gets access token
    public static string GetAccessToken(){
        try {
            ActiveDirectoryCredentials__c credentials = ActiveDirectoryCredentials__c.getInstance('AccessTokenCredentials_Dev');
            string bodyText = 'client_id='+ credentials.client_id__c
                            +'&resource='+ credentials.resource__c
                            +'&client_secret='+credentials.client_secret__c
                            +'&grant_type='+ credentials.grant_type__c;

            Http http = new Http();
            HttpRequest request = new HttpRequest();
            request.setEndpoint(credentials.AccessTokenUrl__c);
            request.setMethod('POST');
            request.setHeader('Content-Type', 'application/x-www-form-urlencoded');
            request.setBody( bodyText);

            HTTPResponse response = http.send (request);           
            string access_token = null;

            if (response.getStatusCode () == 200) {
                    Map <String, Object> jsonParserResult = (Map <String, Object>) SYSTEM.JSON.deserializeUntyped (response.getBody ());
                    System.Debug ('----------'+jsonParserResult );

                    access_token =  (string)jsonParserResult.get ('access_token');
                    System.Debug ('----------'+ access_token);
            }
            return access_token;
        }
            catch (Exception e) {
            System.Debug ('------Exception-----'+e.getMessage ());
            return null;
        }
    }   
}

Here are my test classes I am using to test it:

@isTest
global class MockHttpResponseGenerator implements HttpCalloutMock {
// Implement this interface method
global HTTPResponse respond(HTTPRequest req) {
    // Create a fake response (first mock is access_token, second mock is client id)
    HttpResponse res = new HttpResponse();
    res.setHeader('Content-Type', 'application/json');

    if(req.getBody().contains('Access Token'))
        res.setBody('{"access_token":"somevalidAccessToken"}');
    else if(req.getBody().contains('Client Id')){
        res.setBody('{"client_id" : "12345"}');
    }

    res.setStatusCode(200);

    return res;
}
}


 @isTest
public class BeyondApiServiceTest {
static void testAccessTokenCallout() {
    // Set mock callout class 
    Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());

    // Frst request
    Http h = new Http();        
    HttpRequest req1 = new HttpRequest();
    req1.setEndpoint('http://mygreatservice.com/foo/bar');
    req1.setMethod('POST');
    req1.setBody('Access Token');
    HttpResponse res1 = h.send(req1);
    System.assertEquals('{"access_token":"somevalidAccessToken"}', res1.getBody());

    // Second request       
    HttpRequest req2 = new HttpRequest();
    req2.setEndpoint('http://mygreatservice.com/foo/bar');
    req2.setMethod('POST');
    req2.setBody('Client Id');
    HttpResponse res2 = h.send(req2);
    System.assertEquals('{"client_id" : "12345"}', res2.getBody());

    Test.startTest();

    // Trigger should fire to grab Beyond ID
    Account a = new Account();
    a.Name = 'APITestAccount';
    insert a;     

    Set<Id> accountIds = new Set<Id>();
    accountIds.add(a.Id);

    //BeyondApiService.fetchBeyondClientIds(accountIds);

    Test.stopTest();
}
}

Thanks Suresh

  • Your test class should assert the expected behaviour i.e. a value set in Beyond_Unique_ID__c. Your mock class should also assert the request values and fake the response. – Keith C Jul 16 '17 at 21:30
  • 1
    FYI: best general way to mock callout for test methods: salesforce.stackexchange.com/questions/141330/… - Also what lines are not covered. Reviewing that will help you understand where you need to implement different tests to satisfy the criteria – Eric Jul 16 '17 at 22:28
2

The general issue you have is your code under test does two callouts

GenerateBeyondClientId()  // expects {"client_id" : "someId"}

that immediately calls

GetAccessToken()  // expects {"access_token" : "somestring"}

Your mock HttpCallout class is mocking a common response for both but never mocking a response body that either class is expecting. Instead, it mocks a response via res.setBody('{"foo":"bar"}');

There are a variety of ways to resolve this, for example, keeping a counter in the HttpCalloutMock, returning the appropriate response Body for callout #0, then a different response for callout #1.

For example:

public class MockHttpResponseGenerator implements HttpCalloutMock {
    private Integer mockCount = 0;
    // Implement this interface method
    public HTTPResponse respond(HTTPRequest req) {


        // Create a fake response (first mock is access_token, second mock is client id)
        mockCount++;
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody(mockCount == 1
                    ? '{"access_token":"somevalidAccessToken"}'
                    : '{"client_id" : "someValidClientId"}'  
                   );
        res.setStatusCode(200);
        return res;
    }
}
  • hey, thank for your input. I see what you are saying but I am fairly new to APEX and Salesforce all together. Would you mind providing some type of code snippet to get around that I can use? Thanks in advance. – Suresh Dalal Jul 17 '17 at 12:30
  • 1
    @SureshDalal - you can't be that new to apex if you've figured out how to mock callouts in test methods :-) --updated answer – cropredy Jul 17 '17 at 13:16
  • This didn't work. It still gives me 52% coverage. When I see code coverage, the call to get the access token is not covered mostly. Also made few more changes similar to what you suggested but still same coverage. updated mock and test classes above – Suresh Dalal Jul 18 '17 at 14:16
  • @SureshDalal -- if you edited the OP, your test class isn't right -- you don't make callouts in the test method BeyondApiServiceTest, the underlying code under test makes the callouts and SFDC will use your mocks to return the result rather than making a 'real' callout – cropredy Jul 18 '17 at 15:46
  • I am sorry but would you mind what do you mean by OP above? Also, would you mind taking a look in my Mock and test class above and advise what am I doing wrong? I am not making callouts from BeyondApiServiceTest but just creating a new Account which makes the callouts. Also, if I see code coverage, most of the code that is not covered is from GetAccessToken callout. – Suresh Dalal Jul 19 '17 at 18:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.