+ Start a Discussion
Sisodia SaurabhSisodia Saurabh 

Error in test class for batch apex sending emails to users

Hi,
I have written batch to send emails to users not logged in more than 20 days. it is working when I use schedular  with batch limit 10.
But when I am trying to write test class I am getting below error:
"System.UnexpectedException: No more than one executeBatch can be called from within a test method.  Please make sure the iterable returned from your start method matches the batch size, resulting in one executeBatch invocation."

--- my batch -- 
global class SendEmailtoNonActiveUsersBatch implements database.Batchable<sObject> {
    Exception[] errors = new Exception[0];
    global String query;
    global Database.QueryLocator start(Database.BatchableContext bc) {
        
        string query = 'SELECT id,Firstname,Lastname,Profile.name, email,Isactive, LastLoginDate, category__c,IsEmailSendToNonActive__c FROM User where IsActive=True and Category__c= \'Employee\' and IsEmailSendToNonActive__c != true and LastLoginDate < LAST_N_DAYS:20 order by Firstname ASC ';
        return database.getQueryLocator(query);
    }
    
    global void execute(Database.BatchableContext bc, List<User> scope){
        system.debug('Scope size' +scope.size());
        try{ 
            String currentUserLink = URL.getSalesforceBaseUrl().toExternalForm() + '/';
            if(scope.size() >0){ 
                for(user u: scope){
                    if(u.IsEmailSendToNonActive__c != true){
                        u.IsEmailSendToNonActive__c = true;
                        string[] toAddress = new string[] {u.Email};
                        string subject = 'Notification Of User Deactivation';
                        
                        string plainBody = 'TEST'
                        
                        SendEmail(toAddress,subject, plainBody, null, null );
                        update u;
                    }
                }
            }
        }
        catch(exception e){
            errors.add(e);
        }
    }
    
    global void finish(Database.BatchableContext bc){
        //Admins email addresses
        List<string> emailAdd = new List<string>();
        for(AdminsEmailList__c emad : AdminsEmailList__c.getAll().values())
        {
            system.debug('emad.Email__c:'+emad.Email__c);   
            emailAdd.add(emad.Email__c);
        }
        
        List<AggregateResult> numberOfRows = [Select count(id) from user where IsEmailSendToNonActive__c=True and lastmodifieddate = today];
        integer numRow = (integer)numberOfRows[0].get('expr0');
        
        AsyncApexJob a = [SELECT Id,Status,JobType,NumberOfErrors,JobItemsProcessed,TotalJobItems,CompletedDate,ExtendedStatus
                          FROM AsyncApexJob WHERE Id =:BC.getJobId()];
        
        
        
        //check for errors if no error then proceed
        string errorString;
        for(Exception s: errors)
        {
            errorString = errorString + s.getMessage();  
        }
        if(!errors.isEmpty()) {
            string errSub = 'Error(s) occurred during sending emails to Non Active user batch process.';
            string errBody = 'below is the error details: \n\n'+errorString;
            SendEmail(emailAdd, errSub, errBody, null, null);
        } 
    }
    public void SendEmail(List<string> toEmailAdd, string subject, string body, string attchmentName, string attachment){
        system.debug('toEmailAdd::'+toEmailAdd);
        if(toEmailAdd != null && !toEmailAdd.isEmpty()){
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

            mail.setToAddresses(toEmailAdd);
            mail.setSubject(subject);
            mail.setSaveAsActivity(false);
            mail.setPlainTextBody(body);
 
            
            Messaging.sendEmail(new Messaging.Email[] { mail });
        }
    }
}
--- Schedular --
global class ScheduleSendEmailToNonActiveUsers implements Schedulable{
    
    global void execute(SchedulableContext sc){
        SendEmailtoNonActiveUsersBatch s = new SendEmailtoNonActiveUsersBatch();
        database.executeBatch(s, 10);
    }
    
}

--- TEST CLASS -----
@isTest
public class SendEmailtoNonActiveUsersBatch_Test {
        
    @IsTest(seeAllData=true)
    public static void SendEmailTest_positive()
    {
          User us = [Select id from User where Id = :UserInfo.getUserId()];
        
        System.runAs(us)
        { 
           List<user> uu = [SELECT id,Firstname,Lastname,Profile.name, email,Isactive, LastLoginDate, category__c,IsEmailSendToNonActive__c FROM User where IsActive=True and Category__c= 'Employee' and IsEmailSendToNonActive__c != true and LastLoginDate < LAST_N_DAYS:20 order by Firstname ASC Limit 10];
        Test.startTest();
        SendEmailtoNonActiveUsersBatch s = new SendEmailtoNonActiveUsersBatch();
     
        database.executeBatch(s);
         Test.stopTest();
        }
    }
}

Another observation: test class should execute DML query menstioned in test class(resulte records 10). But when I run the test class it take query mentioned in batch apex(result records 162). 
 
Best Answer chosen by Sisodia Saurabh
ApuroopApuroop
Try changing your start method like below,
global Database.QueryLocator start(Database.BatchableContext bc) {
        if(Test.isRunningTest()){
            string query = 'SELECT id,Firstname,Lastname,Profile.name, email,Isactive, LastLoginDate, category__c,IsEmailSendToNonActive__c FROM User where IsActive=True and Category__c= \'Employee\' and IsEmailSendToNonActive__c != true order by Firstname ASC ';
        }else{
            string query = 'SELECT id,Firstname,Lastname,Profile.name, email,Isactive, LastLoginDate, category__c,IsEmailSendToNonActive__c FROM User where IsActive=True and Category__c= \'Employee\' and IsEmailSendToNonActive__c != true and LastLoginDate < LAST_N_DAYS:20 order by Firstname ASC ';
        }
         return database.getQueryLocator(query);
    }
Try to create just one user without the (seeAlldata=true), just don't use it for testing purpose. Also, make sure that it would fit the WHERE clause that we used in the first if condition. I removed the LastLoginDate just to be sure. I guess it's okay cause it's just for testing.
 

All Answers

ApuroopApuroop
Try this Scheduler once.
global class ScheduleSendEmailToNonActiveUsers implements Schedulable{
    global void execute(SchedulableContext sc){
        if(!Test.isRunningTest()){
            SendEmailtoNonActiveUsersBatch s = new SendEmailtoNonActiveUsersBatch();
        	database.executeBatch(s, 10);
        }
    }
}

There is a chain of batches happening which can't be done in a test class apparently from the error message. 
Sisodia SaurabhSisodia Saurabh
Hi Apuroop,
Thanks for replying. I tried that also but its not working. same error. :(
Sisodia SaurabhSisodia Saurabh
Thing is as soon as I will add "Limit 10" to my query. Test class works fine

 global Database.QueryLocator start(Database.BatchableContext bc) {
        
        string query = 'SELECT id,Firstname,Lastname,Profile.name, email,Isactive, LastLoginDate, category__c,IsEmailSendToNonActive__c FROM User where IsActive=True and Category__c= \'Employee\' and IsEmailSendToNonActive__c != true and LastLoginDate < LAST_N_DAYS:20 order by Firstname ASC LIMIT 10';
        return database.getQueryLocator(query);
    }
ApuroopApuroop
Did you try giving the scope when executing the batch?
Id batchID = database.executeBatch(s, 1); // Lets try with 1  record per batch
Sisodia SaurabhSisodia Saurabh
Alread tried but not working. 
below are my tests and result (which is same for all):
1) @isTest(SeeAllData = false) then in test method  created 10 users. Still it takes query from batch start function. Not test data. and throw above error.
2) @isTest(SeeAllData = true) then in test method queried users with LIMIT 10. Still it takes query from batch start function. Not test data. and throw above error.
3) I ran between test.start and stop,  System.schedule('ScheduleApexClassTest',  CRON_EXP, new ScheduleSendEmailToNonActiveUsers()); Still it takes query from batch start function. Not test data. and throw above error.
4) I ran between test.start and stop,  database.executeBatch(s, 1); Still it takes query from batch start function. Not test data. and throw above error.
In Short no luck :(
ApuroopApuroop
Try changing your start method like below,
global Database.QueryLocator start(Database.BatchableContext bc) {
        if(Test.isRunningTest()){
            string query = 'SELECT id,Firstname,Lastname,Profile.name, email,Isactive, LastLoginDate, category__c,IsEmailSendToNonActive__c FROM User where IsActive=True and Category__c= \'Employee\' and IsEmailSendToNonActive__c != true order by Firstname ASC ';
        }else{
            string query = 'SELECT id,Firstname,Lastname,Profile.name, email,Isactive, LastLoginDate, category__c,IsEmailSendToNonActive__c FROM User where IsActive=True and Category__c= \'Employee\' and IsEmailSendToNonActive__c != true and LastLoginDate < LAST_N_DAYS:20 order by Firstname ASC ';
        }
         return database.getQueryLocator(query);
    }
Try to create just one user without the (seeAlldata=true), just don't use it for testing purpose. Also, make sure that it would fit the WHERE clause that we used in the first if condition. I removed the LastLoginDate just to be sure. I guess it's okay cause it's just for testing.
 
This was selected as the best answer
Sisodia SaurabhSisodia Saurabh
It worked. Although I still dont know why I am got that error on first place.
Thanks, for replies :)