Unit Test for Basic Atarc Process


Since ATARC is very coupled to the custom metadata type ATARC Process Settings, custom metadata types in saleforce are actually test visible by default, this means that its data is accessible by the tests even if they don't have SeeAllData=true.

This gives us two options in order to test an apex helper class for Atarc Process:

  1. Let ATARC use org specific custom metadata type records, when the test runs the records in the ATARC Process Settings are going to be what we have in the org.
  2. Don't use org specific custom metadata type records, ATARC is more in favor of not using org specific information in order to perform testing, we have found creative ways to not depend on org specific data which we will cover as part of this tutorial.

Before you begin:

We are assuming you have completed the Basic Atarc Process tutorial. We will create an apex unit test class for OpportunityPricebookManager_AP apex class which is part of the previously mentioned tutorial.

Unit Test Apex Class, ATARC Process Settings data taken from the org

Let's dive right into the test class keeping in mind the first option where the test class is assuming ATARC Process Settings custom metadata type data is available in the org, then we will explain in detail how it works:


/*
 * Created By : anyei
 * Created Date: 03/07/2020
 * ATARC Process Tested: OpptyPricebookMgr
 * Tested Class OpportunityPricebookManager_AP
   Description: Assuming the ATARC Process Settings has the appropriate data, a record for OpptyPricebookMgr Atarc Process, which ATARC should find during the test run.
 */ 
@IsTest
public class OpportunityPricebookManager_AP_Test extends AsyncTriggerArc.AsyncTriggerArcProcessBase {
    
    //preparing additional data needed, like the pricebooks
    @testsetup static void SetupData(){
        //ATARC Global Settings is hierarchy custom setting we should add a record for our test
        ATARC_Global_Setting__c globalSetting = new ATARC_Global_Setting__c(Debug__c=false, LoopLimit__c=1, SetupOwnerId=UserInfo.getOrganizationId());
        insert globalSetting;
        
        //creating pricebooks records for later usage
        List<Pricebook2> pricebooks =new List<Pricebook2> {
            new Pricebook2(Name = 'Pricebook A for Testing', IsActive=true),
            new Pricebook2(Name = 'Pricebook B for Testing', IsActive=true)
        };
        insert pricebooks;
    }
    
    //test scenario 1
    @IsTest static void PricebookIsCalculatedForNewOppty(){
        string pricebookAId;
        string pricebookBId;
        for(Pricebook2 pricebookRecord : [select id, name from Pricebook2 where Name in ('Pricebook A for Testing','Pricebook B for Testing')]){
            if(pricebookRecord.Name == 'Pricebook A for Testing') pricebookAId = pricebookRecord.Id;
            if(pricebookRecord.Name == 'Pricebook B for Testing') pricebookBId = pricebookRecord.Id;
        }
        
        //making sure the mapping variable has accurate pricebook ids 
         List<Opportunity_Market_Pricebook_Mapping__mdt> testOpptyPricebookMappingList=   new List<Opportunity_Market_Pricebook_Mapping__mdt>{
          new Opportunity_Market_Pricebook_Mapping__mdt (DeveloperName='Orlando', PricebookId__c=pricebookAId, isActive__c=true),
          new Opportunity_Market_Pricebook_Mapping__mdt (DeveloperName='Internationals', PricebookId__c=pricebookBId, isActive__c=true)
        };
         
        //I want my apex helper class to use this local mapping instead and not doing any query to the org
        //we can do this because opptyPricebookMappingList has @TestVisible attribute
        //and made static intentionally in the tutorial Basic Atarc Process.
        OpportunityPricebookManager_AP.opptyPricebookMappingList = testOpptyPricebookMappingList;
        
        List<Opportunity> oppties = new List<Opportunity>{
           new Opportunity(Name='Test oppty 1', CloseDate=Date.today().AddDays(10), StageName='Identify', Market__c='Orlando'),
           new Opportunity(Name='Test oppty 2', CloseDate=Date.today().AddDays(10), StageName='Identify', Market__c='Internationals')
        };
            
        test.starttest();
        insert oppties;
        test.stoptest();
        
        oppties = [select name,market__c, pricebook2id from opportunity];
        
        //making sure that after creating the opportunities the Atarc Process ran, and the pricebook2id field has been succesfully updated
        //with the expected pricebook coming from the mapping variable
        system.assertEquals( pricebookAId , oppties[0].pricebook2Id );
        system.assertEquals( pricebookBId , oppties[1].pricebook2Id );
    }

}

Running this apex test class should result in 100% code coverage for our Atarc Process apex helper class OpportunityPricebookManager_AP. Everything seems to be in the right place, when the opportunity records are inserted the ATARC engine is kicking off the Atarc Process and its doing the magic of dynamically assining the pricebook to the opportunities based on the market field which we defined the mapping for in the custom metadata type, EXCELENT!

Now, let's look deep at the previous test class, we are depending on the ATARC Process Settings data in the org so that ATARC knows when to execute our apex helper OpportunityPricebookManager_AP. In fact, as of now, apex is not allowed to perform dml statements upon custom metadata types, this is an official restrictions from salesforce.

But as stated before, ATARC is in favor of not depending on the org's data so how to get around that salesforce restriction of no apex you can't dml on custom metadata types. Well, we are doing something pretty clever already to have our own Opportunity Market Pricebook Mapping data and let's not forget this is a custom metadata type as well. As you may recall from the Basic Atarc Process tutorial, Opportunity_Market_Pricebook_Mapping__mdt is a custom metadata type sobject and we intentionally created a static variable opptyPricebookMappingList this was for our tests to inject some data and forget about doing soql to the org.

ATARC also provides this particular mechanism where it exposes a variable for our tests to inject with ATARC Process Settings records (exactly what we intentionally did with OpportunityPricebookManager_AP.opptyPricebookMappingList variable).

Now let's look at how we can inject some test ATARC Process Settings records to ATARC engine

Unit Test Apex Class, ATARC Process Settings data injected by the test class


    /*
 * Created By : anyei
 * Created Date: 03/07/2020
 * ATARC Process Tested: OpptyPricebookMgr
 * Tested Class OpportunityPricebookManager_AP
   Description: Dynamically injecting the ATARC Process Settings information, zero dependency between the org's custom metadata type and this test
 */ 
@IsTest
public class OpportunityPricebookManager_AP_I_Test extends AsyncTriggerArc.AsyncTriggerArcProcessBase {
    
    @testsetup static void SetupData(){
        //ATARC Global Settings is hierarchy custom setting we should add a record for our test
        ATARC_Global_Setting__c globalSetting = new ATARC_Global_Setting__c(Debug__c=false, LoopLimit__c=1, SetupOwnerId=UserInfo.getOrganizationId());
        insert globalSetting;
        
        //creating pricebooks records for later usage
        List<Pricebook2> pricebooks =new List<Pricebook2> {
            new Pricebook2(Name = 'Pricebook A for Testing', IsActive=true),
            new Pricebook2(Name = 'Pricebook B for Testing', IsActive=true)
        };
        insert pricebooks;
    }
    
    @IsTest static void PricebookIsCalculatedForNewOppty(){
        
        //Removing dependency between org's custom metadata type data and this unit test!!
        AsyncTriggerArc.settings = new List<ATARC_Process_Setting__mdt>{
            new ATARC_Process_Setting__mdt(Label='Test ATARC Process', DeveloperName='OpptyPricebookMgr',IsActive__c=true, General_Availability__c=true, ApexHelperClassName__c='OpportunityPricebookManager_AP', Event__c='BeforeInsert', SObject__c='Opportunity', Order__c=1)
        };
            
        string pricebookAId;
        string pricebookBId;
        for(Pricebook2 pricebookRecord : [select id, name from Pricebook2 where Name in ('Pricebook A for Testing','Pricebook B for Testing')]){
            if(pricebookRecord.Name == 'Pricebook A for Testing') pricebookAId = pricebookRecord.Id;
            if(pricebookRecord.Name == 'Pricebook B for Testing') pricebookBId = pricebookRecord.Id;
        }
        
        //making sure the mapping variable has accurate pricebook ids 
        List<Opportunity_Market_Pricebook_Mapping__mdt> testOpptyPricebookMappingList=   new List<Opportunity_Market_Pricebook_Mapping__mdt>{
          new Opportunity_Market_Pricebook_Mapping__mdt (DeveloperName='Orlando', PricebookId__c=pricebookAId, isActive__c=true),
          new Opportunity_Market_Pricebook_Mapping__mdt (DeveloperName='Internationals', PricebookId__c=pricebookBId, isActive__c=true)
        };
         
        //I want my apex helper class to use this local mapping instead
        //we can do this because opptyPricebookMapping has @TestVisible attribute
        //and made static intentionally in the tutorial Basic Atarc Process.
        OpportunityPricebookManager_AP.opptyPricebookMappingList = testOpptyPricebookMappingList;
        
        List<Opportunity> oppties = new List<Opportunity>{
           new Opportunity(Name='Test oppty 1', CloseDate=Date.today().AddDays(10), StageName='Identify', Market__c='Orlando'),
           new Opportunity(Name='Test oppty 2', CloseDate=Date.today().AddDays(10), StageName='Identify', Market__c='Internationals')
        };
            
        test.starttest();
        insert oppties;
        test.stoptest();
        
        oppties = [select name,market__c, pricebook2id from opportunity];
        
        //making sure that after creating the opportunities the Atarc Process ran, and the pricebook2id field has been succesfully updated
        //with the expected pricebook coming from the mapping variable
        system.assertEquals( pricebookAId , oppties[0].pricebook2Id );
        system.assertEquals( pricebookBId , oppties[1].pricebook2Id );
    }

}

So what is different about this test class is the following lines we have added at the begining of the test method:


       //Removing dependency between org's custom metadata type data and this unit test!!
        AsyncTriggerArc.settings = new List<ATARC_Process_Setting__mdt>{
            new ATARC_Process_Setting__mdt(Label='Test ATARC Process', DeveloperName='OpptyPricebookMgr',IsActive__c=true, General_Availability__c=true, ApexHelperClassName__c='OpportunityPricebookManager_AP', Event__c='BeforeInsert', SObject__c='Opportunity', Order__c=1)
        };

By setting a value to the static variable AsyncTriggerArc.settings we are injecting what particular apex helpers to run by ATARC thus it will not look for the org's specific records.

Running the previous test should result in 100% code coverage for our apex helper OpportunityPricebookManager_AP just like the first apex test.

If you are a little bit curious, comment the line 29 of the test class OpportunityPricebookManager_AP_I_Test where we are instantiating a new ATARC_Process_Setting__mdt record and run it, you will noticed a result of 0% code coverage, as ATARC will not find any ATARC Process Setting records therefore OpportunityPricebookManager_AP apex helper will not be executed