0

I am writing a test class for testing webservice callouts as a part of one of the challenges of Data Integration Specialist superbadge. Here I am creating test data by inserting some custom object records in a test data utility class. Following is my code:

Test class:

@isTest
private class BillingCalloutServiceTest {

@isTest(seeAllData = true)
static void positiveTest(){

    Test.setMock(WebServiceMock.class, new BillingCalloutServiceMock());

    List<Project__c> testProjectList = TestDataFactory.createTestProjectData();
    Set<Id> idSet = new Set<Id>();
    for(Project__c proj : testProjectList){
        idSet.add(proj.Id);
        proj.status__c = 'Billable';
    }
    test.startTest();
    update testProjectList;
    test.stopTest();

    for(Project__c proj : [select status__c from Project__c where id in: idSet]){
        system.assertEquals('Billed', proj.Status__c);
    }
}   

}

Test Data Utility class:

@isTest
public class TestDataFactory {
    //Own code
    public static List<Project__c> createTestProjectData(){
        List<Project__c> testProjectList = new List<Project__c>();
        Project__c project1 = new Project__c(Name = 'Test Project 2', Opportunity__c = '0067F000007HlExQAK');
        Project__c project2 = new Project__c(Name = 'Test Project 3', Opportunity__c = '0067F000007HlF5QAK');
        Project__c project3 = new Project__c(Name = 'Test Project 4', Opportunity__c = '0067F000007HlFDQA0');
        testProjectList.add(project1);
        testProjectList.add(project2);
        testProjectList.add(project3);
        insert testProjectList;
        return testProjectList;
    }
}

This works correctly. But if I remove (seeAlldata = true) from @isTest annotation, I get null pointer exception in my apex class that is to be tested as it does not get any data :

enter image description here

Apex trigger that further calls webservice callout code that is to be tested:

trigger ProjectTrigger on Project__c (after update) {
    //Call the Billing Service callout logic here
    ////Own code
    Map<String, Double> refToBillAmountMap = new Map<String, Double>();
    String oldStatus;
    String newStatus;
    ServiceCredentials__c cred = ServiceCredentials__c.getValues('BillingServiceCredential');

    for(Project__c project : trigger.new){
        oldStatus = trigger.oldMap.get(project.Id).Status__c; 
        newStatus = trigger.newMap.get(project.Id).Status__c;
        if(oldStatus != newStatus && newStatus == 'Billable'){
            refToBillAmountMap.put(project.ProjectRef__c, project.Billable_Amount__c);
        }
    }
    if(!refToBillAmountMap.keySet().isEmpty())
    BillingCalloutService.callBillingService(refToBillAmountMap,cred.Username__c,cred.Password__c);
}

As per this trailhead, no where is the seeAllData = true tag put, and it is working. What is the reason I am getting this exception? Also, it does not make sense to use this tag as we are not using org data, but rather creating it ourselves in test class.

Callout service:

global class BillingCalloutService {
    //Implement business and callout logic methods here 
    //Own code    
    public static void callBillingService(Map<String, Double> refToBillAmountMap,String uname,String pswd){ 
        billProject(refToBillAmountMap,uname,pswd);
    }

    @future(callout = true)
    private static void billProject(Map<String, Double> refToBillAmountMap,String uname,String pswd){
        Set<String> projectRefSet = new Set<String>();
        Boolean doUpdate = true;
        try{
            for(String projectRef : refToBillAmountMap.keySet()){
                BillingServiceProxy.project project = new BillingServiceProxy.project();
                project.username = uname;
                project.password = pswd;
                project.projectRef = projectRef;
                project.billAmount = refToBillAmountMap.get(projectRef);
                BillingServiceProxy.InvoicesPortSoap11 callout = new BillingServiceProxy.InvoicesPortSoap11();
                String status = callout.billProject(project);
                if(status.equalsIgnoreCase('OK')){
                    projectRefSet.add(projectRef);
                }
                else{
                    doUpdate = false;
                }
            }

            if(doUpdate){
                List<Project__c> projectListToBeUpdated = new List<Project__c>();
                for(Project__c proj : [select status__c 
                                       from Project__c
                                       where ProjectRef__c in: projectRefSet]){
                                           proj.status__c = 'Billed';
                                           projectListToBeUpdated.add(proj);
                                       }
                update projectListToBeUpdated;
            }
        }
        catch(DmlException e){
            system.debug('Exception DML : ' + e.getMessage());
        }
        catch(CalloutException e){
            system.debug('CalloutException : ' + e.getMessage());
        }
    }    
}
  • 1
    Your unit test setup code needs to create the Opportunities - you shouldn't be reliant on opportunity records existing in the org. – Mark Pond Dec 30 '17 at 6:51
  • @MarkPond : I AM creating the custom object Project__c(if that's what you mean by opportunities) in my test data utility class and am not relied on any org data. – Sarang Dec 30 '17 at 9:22
5

Your trigger l ine:

ServiceCredentials__c cred = ServiceCredentials__c.getValues('BillingServiceCredential');

is fetching a Custom Setting. You need to mock values of this custom setting in your testmethod. You should not rely on org data (seeAllData=true) except in the most unusual of circumstances.

You can mock custom settings like any other SObject by doing an insert in your testmethod

insert new ServiceCredentials__c (fldx = val, fldY = val, ...);

The field values to mock should be the ones needed to fetch the custom setting and the fields used in your callout service call on line 17

  • 1
    Also, better to use @testsetup where test data can be created calling TestDataFactory methods and data could be utilized in testmethods – Santanu Boral Dec 30 '17 at 7:02
  • @cropredy : One basic doubt here(probably a silly one), How could my test class access(line 37 in newly added code) the Project__c records in my callout service class without the seeAllData? The assertion in test class turned out to be true implying update statement was executed. – Sarang Dec 30 '17 at 9:17
  • 1
    @sarang - SeeAllData=true means that any sobject in the org including those that existed prior to the test being run as well as those inserted as part of the test are available. There is almost no reason to ever use SeeAllData=true and in fact using it can make tests fail when you deploy to PROD. I also note your callout class is eating exceptions instead of reporting them in such a way as to alert a sysad. – cropredy Dec 30 '17 at 11:16

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.