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
Jan StaufenbergJan Staufenberg 

How to access values that have been set during a run of a batchable apex-class using Database.executeBatch()?

Hi all,
I'm facing problems on accessing values of an object I passed to Database.executeBatch() method in a test class.
The following code sample is my Batchable Apex Class for sending eMails:
public without sharing class EmailHandler implements Schedulable, Database.batchable<Attachment>, Database.AllowsCallouts, Database.Stateful {
	
	/* Contains the total number of sent eMails for the last executed run */
	private Integer SentMailsLastRun;
	
	/* Constructs the eMail-Handler, initializing the count of eMails that have been sent during this run */
	public EmailHandler() {
		//Initialize count of sent mails for this run
		SentMailsLastRun = 0;
	}
	
	/*
	 * Starts the batch processing of attachments, collecting all attachments to process.
	 */
    public Iterable<Attachment> start(Database.BatchableContext BC){
       System.debug(LoggingLevel.INFO,'EmailHandler.start: Reading attachments to process in batch...');
       List<Attachment> attachmentList = processAttachments();
       if(attachmentList!=null) {
       	System.debug(LoggingLevel.INFO,'EmailHandler.start: Collected ' + attachmentList.size() + ' Attachments to process in batch-execution.');
       } else {
       	System.debug(LoggingLevel.INFO,'EmailHandler.start: No Attachments to process in batch-execution.');
       }
       if(attachmentList == null){
       		attachmentList = new List<Attachment>();	 
       }
       return attachmentList;
    }
    
    /*
     * Executes the eMail sending for a given batch of attachments. 
     */
    public void execute(Database.BatchableContext BC, List<Attachment> attachmentList){
       if(attachmentlist != null && attachmentList.size() > 0){
       		System.debug(LoggingLevel.INFO,'EmailHandler.execute: Batch execution started. Batch Size: ' + attachmentList.size());
       		emailServiceWorkOrder(attachmentList);
       		System.debug(LoggingLevel.INFO,'EmailHandler.execute: Batch processing of ' + attachmentList.size() + ' Attachments finished.');
       }else{
       	  System.debug(LoggingLevel.INFO,'EmailHandler.execute: No Attachments to process in this batch.');
       } 
    }

    /*
     * Is called after batch processing is finished.
     */
    public void finish(Database.BatchableContext BC){
    	 System.debug(LoggingLevel.INFO,'EmailHandler.finish: Batch execution finished.');
    }
    
    /*
     * Is called from system when its time to execute the planned job.
     */
    public void execute(SchedulableContext SC) {
    	System.debug(LoggingLevel.INFO,'EmailHandler.execute(SchedulableContext SC): Scheduled Job execution started.');
        EmailHandler emailhandler = new EmailHandler();
        Database.executebatch(emailhandler, 100);
        System.debug(LoggingLevel.INFO,'EmailHandler.execute(SchedulableContext SC): Batch execution triggered.');
    }

    /* Returns the number of mails sent the last run, if execution is still running returns -1 */
    public Integer getSentMails() {
        return SentMailsLastRun;
    }

    /*
     * Collects the attachments for Work Orders in status 'Closed' that have been modified after the last run time of the job.
     */
	private List<Attachment> processAttachments(){
		
[...] collecting of attachments [...]
		
		return attachmentList;
	}

	/*
	 * Processes the attachments passed over: Sends the attachments configured in 'BBMAG_Email_Notification_Settings__c' as 'Service Report Name'
	 * to the addresses configured in the Work Order.
	 */
	private void emailServiceWorkOrder(List<Attachment> newList){

			[...] preparing of eMail-Messages [...]

		//Send Emails
		if (messagesList.size() > 0){
			System.debug(LoggingLevel.INFO,'EmailHandler.emailServiceWorkOrder: Total Emails to be sent: ' + messagesList.size());
			Messaging.sendEmail(messagesList);
			System.debug(LoggingLevel.INFO,'EmailHandler.emailServiceWorkOrder: ' + messagesList.size() + ' Email sent.');
			SentMailsLastRun += messagesList.size();
			System.debug(LoggingLevel.INFO,'EmailHandler.emailServiceWorkOrder: In this run ' + getSentMails() + ' Emails have been sent in total.');
		}
	}
This is how I call Database.executeBatch() from Test-Class:
EmailHandler emailhandler = new EmailHandler();
        Test.StartTest();
        Database.executebatch(emailhandler);
        Test.StopTest();
        
        System.assertEquals(2,emailhandler.getSentMails(),'The expected count of eMails that should\'ve been sent did not match');

The log-output shows, that the variable "SentMailsLastRun" of the EmailHandler-Object has been set correctly and is read correctly from within the class by method getSentMails():
16:47:34:307 USER_DEBUG [521]|INFO|EmailHandler.emailServiceWorkOrder: In this run 2 Emails have been sent in total.
16:47:34:340 EXCEPTION_THROWN [35]|System.AssertException: Assertion Failed: The expected count of eMails that should've been sent did not match: Expected: 2, Actual: 0
Nevertheless, if the method is called from the test-class it delivers the value that has been set by the constructor (0). Does Database.executeBatch() clone the passed object and continues working with the clone?!
How may I access data collected during this process afterwards (Like in my case totally sent eMails) to verify the correctness?

Thanks in advance!
Best Answer chosen by Jan Staufenberg
V V Satyanarayana MaddipatiV V Satyanarayana Maddipati
Hi Jan,

Even i came across the similar issue long back. But don't know what happening after Finish() method is executed.
The workaround for this issue is to move assert statement to Finish() in the batch class and make sure  which runs only in the Testing Context. Below is the code:
 
public void finish(Database.BatchableContext BC) {
        if(Test.isRunningContext()) {
            System.assertEquals(2,emailhandler.getSentMails(),'The expected count of eMails that should\'ve been sent did not match');
        }
    }

Thanks
Satya

 

All Answers

Nayana KNayana K
Change variable declaration from private to global and check once
global intetger SentMailsLastRun;

 
V V Satyanarayana MaddipatiV V Satyanarayana Maddipati
Hi Jan,

Even i came across the similar issue long back. But don't know what happening after Finish() method is executed.
The workaround for this issue is to move assert statement to Finish() in the batch class and make sure  which runs only in the Testing Context. Below is the code:
 
public void finish(Database.BatchableContext BC) {
        if(Test.isRunningContext()) {
            System.assertEquals(2,emailhandler.getSentMails(),'The expected count of eMails that should\'ve been sent did not match');
        }
    }

Thanks
Satya

 
This was selected as the best answer
Jan StaufenbergJan Staufenberg
Hey Nayana K,
Thanks for the reply! Already tried this, it didn't work as well. If the value was inaccessible, I would have expected a null return or an Exception, but it was set correctly as the Debug-Log shows. And as the return value equals the initial value set by the constructor when calling the method from Test-Class the only logical explanation I see is that a clone of the passed object is used for processing...
Jan StaufenbergJan Staufenberg
Hi V V Satyanarayana Maddipati,

This would be a workaround. Its a pity as I will have to store the expected result in the object as well, but I guess this would at least solve the issue.

Thanks for your help!
Jan