function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Robert Davis 1Robert Davis 1 

Test Class for Batch Process

I have a batch class that I have tested in a Sandbox with 10,000 records and it appears to be functioning as it should. My issue now is I can not seem to figure out the Test Class.

What basically happens is we have Sales Agents that are created as part of a custom Object. I go to the Account that these Sale Agents records are related to and create Accounts for each one and match the original Sales Agent record to the new Account.

I store the id for the Account in a Custom Setting.

The Batch Process:
global class CreateAccountsNewlyCreatedDSS implements 
    Database.Batchable<sObject>, Database.Stateful {
    
    public disc2__c disc2 = disc2__c.getValues('DSS Agent Codes Account ID');
    global ID AcctID = disc2.DSS_AccountID__c;
    
    // instance member to retain state across transactions
    global Integer recordsProcessed = 0;
    global Database.QueryLocator start(Database.BatchableContext bc) 
    {
        return Database.getQueryLocator(
            'SELECT id ,'               +
                    'ssn__c,'           +
                    'agent__c,'         + 
                    'ag_first_name__c,' +
                    'ag_last_name__c,'  +
                    'agent_name__c '    +
            'FROM agent_code__c '       +
            'WHERE agent__c =: AcctID'
        ); 
    }
    global void execute(Database.BatchableContext bc, List<Agent_Code__c> scope)
    {
        // process each batch of records
        map<string, account> ma = new map<string, account>();
        for (agent_code__c ac: scope) 
        {
            
                account a = new account();
                a.AG_First_Name__c = ac.ag_first_name__c;
                a.AG_Last_Name__c  = ac.ag_last_name__c;
                a.Name             = ac.Agent_Name__c;
                a.SSN__c           = ac.ssn__c;
                ma.put(a.SSN__c, a);
                
                
                
                recordsProcessed = recordsProcessed + 1;
        }
        Database.UpsertResult[] upsAgtCode = Database.upsert(ma.values(),account.fields.ssn__c, false);
        for(agent_code__c ac1: scope)
        {
            ac1.agent__c=ma.get(ac1.ssn__c).id;
        }
        database.update(scope, false);
        
        
    }
       
        
    global void finish(Database.BatchableContext bc){
        System.debug(recordsProcessed + ' records processed. Shazam!');
        List<Agent_Code__c> AgtCde = [SELECT id, Agent__c, Name FROM Agent_Code__c WHERE Agent__c =: AcctID];
        AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, 
            JobItemsProcessed,
            TotalJobItems,
            CreatedBy.Email
            FROM AsyncApexJob
            WHERE Id = :bc.getJobId()];
        // call some utility to send email
        List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();
        for(User usr: [SELECT id, email FROM User WHERE Profile.Name = 'System Administrator'])
        {
            
            
            System.debug('User Array of the System Administrators '+ usr);
            Messaging.SingleEmailMessage mail2 = new Messaging.SingleEmailMessage();
            List<String> sendTo = new List<String>();
            sendTo.add(usr.Email);
            mail2.setToAddresses(sendTo);
            List<String> ccTo = new List<String>();
            ccTo.add('robert.d@XXX.com');
            mail2.setCcAddresses(ccTo);
            mail2.setSubject('Apex Batched Job - Move Contacts from Distribution Supply Source Account');
            string body = 'The Apex Batch Job named \'CreateAccountsNewlyCreatedDSS.apxc\' moves agent codes ';
                   body += 'from the DSS Account and creates them as their own accounts. This process runs daily ';
                   body += 'and was once a manual process. ';
                   body += 'Below are the specifics of this batch job : <br/><br/>';
            body += 'Apex Job '+ job + '<br/><br/>';
            if(AgtCde.size() > 0)
            {
                body += 'List of Agent Codes that were not moved and need to be investigated : <br/><br/> ';
                for(Agent_Code__c ac: AgtCde)
                {
                    body += '    This record id <b>' + ac.Id + '</b> was not moved. <br/>';
                    body += '    This is the Agent Code that did not process <b>'+ ac.Name+'</b>.<br/><br/>';
                }
            } else 
            {
                body += '<b>No Records were left behind</br>. All records matched the requirements to create Accounts. ';    
            }
            
            body += '<br/><br/>For more information on this process and how it should work go to the Admin Routines in ';
            body += 'New Parts Salesforce Org and go to #NewAgentCodes:MovefromTheNewAgentCodeAccountBucket .';
            mail2.setHtmlBody(body);
            mails.add(mail2);
            
        }
        Messaging.sendEmail(mails);
    }    
}

Batch Schedule:
global class MoveAgentCodesBatchScheduler  implements Schedulable
{
    global void execute(SchedulableContext sc)
    {
        // Implement any logic to be scheduled
       
        // We now call the batch class to be scheduled
        CreateAccountsNewlyCreatedDSS b = new CreateAccountsNewlyCreatedDSS();
       
        //Parameters of ExecuteBatch(context,BatchSize)
        database.executebatch(b,2000);
    }
   
}
Test Class that not only seems to have to many DML statements but also de-reference a null object.
@isTest
public class CreateAccountsNewlyCreatedDSS_Batch_Test 
{
    
    @testSetup
    
    static void setup()
    {
        Account acct = New Account(Name= 'Test Account'
        );
        insert acct;
        
        List<Agent_Code__c> lstAgtCd = new List<Agent_Code__c>();
        for(Integer i=0; i<20; i++)
        {
            lstAgtCd.add(new Agent_Code__c(Agent__c = acct.id,
                                           Name = 'AgentTestCode'+i,
                                           ag_first_name__c = 'AgentFirstTestName'+i,
                                           ag_last_name__c = 'AgentLastTestName'+i,
                                           ssn__c = i+'2341'
            ));
        }
        
        insert lstAgtCd;      
    }
            static testmethod void test()
        {
            Test.startTest();
            CreateAccountsNewlyCreatedDSS testBatch = new CreateAccountsNewlyCreatedDSS();
            Id batchprocessId = Database.executeBatch(testBatch,10000);
            Test.stopTest();
            
        }
    
}
Your help is appreciated. Thank you in advance for any insight.

Robert

 
Best Answer chosen by Robert Davis 1
Alain CabonAlain Cabon
Hi,

Your problem is the custom settings at the beginning that are not resolved.

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_custom_settings.htm
 
    public disc2__c disc2 = disc2__c.getValues('DSS Agent Codes Account ID');
    global ID AcctID = disc2.DSS_AccountID__c;  // de-reference a null object here
You can try this:
 @testSetup static void setup() { 

     Account acct = New Account(Name= 'Test Account' ); 
     insert acct;

     disc2__c  obj = new disc2__c (Name = 'DSS Agent Codes Account ID');
     insert obj;
     disc2__c t = disc2__c.getInstance('DSS Agent Codes Account ID');
     t.DSS_AccountID__c= acct.Id;
     update t;

...

All Answers

Alain CabonAlain Cabon
Hi,

Your problem is the custom settings at the beginning that are not resolved.

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_custom_settings.htm
 
    public disc2__c disc2 = disc2__c.getValues('DSS Agent Codes Account ID');
    global ID AcctID = disc2.DSS_AccountID__c;  // de-reference a null object here
You can try this:
 @testSetup static void setup() { 

     Account acct = New Account(Name= 'Test Account' ); 
     insert acct;

     disc2__c  obj = new disc2__c (Name = 'DSS Agent Codes Account ID');
     insert obj;
     disc2__c t = disc2__c.getInstance('DSS Agent Codes Account ID');
     t.DSS_AccountID__c= acct.Id;
     update t;

...
This was selected as the best answer
Robert Davis 1Robert Davis 1
Alain,

Thank you, so very much.

Robert
Robert Davis 1Robert Davis 1
Alain,

The code now passes test but it does not seem to be creating the agent codes and attaching them to the account for the process to loop through and act on. Any thoughts on how this might be solved?

User-added image

Robert 
Alain CabonAlain Cabon
Hi Robert,

It seems that disc2.DSS_AccountID__c is still null (?).
 
static testmethod void test()
        {

            disc2__c disc21 = disc2__c.getValues('DSS Agent Codes Account ID');
            ID AcctID1 = disc21.DSS_AccountID__c;
            System.assert(AcctID1 != null,'error: DSS_AccountID__c is null');
            System.debug('disc2.DSS_AccountID__c:' + AcctID1);

            Test.startTest();
            CreateAccountsNewlyCreatedDSS testBatch = new CreateAccountsNewlyCreatedDSS();
            Id batchprocessId = Database.executeBatch(testBatch,10000);
            Test.stopTest();
            
        }

What is the result?
 
Robert Davis 1Robert Davis 1
Alain,

No longer am I getting I am getting 61% Code Coverage but the test code is not accessing the following area of code and not processing any records to move:
 
global void execute(Database.BatchableContext bc, List<Agent_Code__c> scope)
    {
        // process each batch of records
        map<string, account> ma = new map<string, account>();
        for (agent_code__c ac: scope)
        {
            
                account a = new account();
                a.AG_First_Name__c = ac.ag_first_name__c;
                a.AG_Last_Name__c = ac.ag_last_name__c;
                a.Name            = ac.Agent_Name__c;
                a.SSN__c          = ac.ssn__c;
                ma.put(a.SSN__c, a);                                        

                recordsProcessed = recordsProcessed + 1;

        }

        Database.UpsertResult[] upsAgtCode = Database.upsert(ma.values(),account.fields.ssn__c, false);

        for(agent_code__c ac1: scope)

        {

            ac1.agent__c=ma.get(ac1.ssn__c).id;

        }

        database.update(scope, false);
    }

The code is skipping executing this part of the batch class.

Thanks
Robert
Alain CabonAlain Cabon
Could you add these lines (like above) and relaunch the test?

If that fails, that means that disc21.DSS_AccountID__c is null and that is the problem.
 
            disc2__c disc21 = disc2__c.getValues('DSS Agent Codes Account ID');
            ID AcctID1 = disc21.DSS_AccountID__c;
            System.assert(AcctID1 != null,'error: DSS_AccountID__c is null');
            System.debug('disc2.DSS_AccountID__c:' + AcctID1);

If the request returns no result for the query locator then the batch could not be launched and the request returns nothing if disc21.DSS_AccountID__c is not correct (null value)(tested with the lines above).

List<Agent_Code__c> scope is empty if the query locator returns nothing because the global ID AcctID = disc2.DSS_AccountID__c; is null probably.
Robert Davis 1Robert Davis 1
I added the above code to the Test Class and it failed at the Assert. But the Debug returned a AccountID. Forgive me for asking, but doesn't the Assert say if AcctID1 does not equal null then return this message. So doesn't that mean it did have an Account ID?

 
Alain CabonAlain Cabon

assert(condition, msg): Asserts that the specified condition is true. If it is not, a fatal error is returned that causes code execution to halt.

Logically,  AcctID1 is null (bad) and so that failed at the Assert but you see that the Debug returned an AccountID (?)

The initialisation of DSS_AccountID__c in the setup could be wrong finally.
DSS_AccountID__c exists now (no error of de-reference null) but with a wrong value.
 
If you remove the assert or change   System.assertEquals(null, AcctID1);  that is alway ok ?
or System.assertNotEquals(null, AcctID1);

If you see a correct AccountID for the request, that should work.

You can try to move all the setup code just before your Test.startTest();​

If AcctID is not null, that should work because there are many records into  agent__c   ( insert lstAgtCd;  ) 

Verify with this assert : System.assertNotEquals( 0 , [select count() from agent_code__c where agent__c = : AcctID1);
 
Robert Davis 1Robert Davis 1
Alain,

Thanks for all the help. I wanted to follow up and let you know what the issue is / was. I created the Account ID, I then created the Custom Setting, I then assigned a variable to the custom setting field, but never did a Update.

So the following line of code fixed all the issues:
 
disc2__c obj = new disc2__c(Name='DSS Agent Codes Account ID',DSS_AccountID__c = acct.id);
        insert obj;

Thank you for your time and help. Ohana.

Robert​