2

I made batch class, and It's working very well. But when I try to execute it in test class, Batch apex not getting executed from test class, and it does not see test data. I've made two versions of my test class. First is written by me, second class was written with help of this article(Batch apex not getting executed from test class).But none of those is successful.

Batch class

global class EmailDuplicatorcontroller implements Database.Batchable<sObject>{
   
    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator([SELECT Email__c FROM Account WHERE Email__c != null AND Email__c !='gdpr@gdpr.com' ] );
    }  

    global void execute(Database.BatchableContext bc, List<Account> Emails){
        for (Account Email: Emails){                    
        
        list<Account> AccountWithEmail = new list<Account>([SELECT Email__c FROM Account WHERE Email__c =: Email.Email__c]);
            if (AccountWithEmail.size() > 1){
             
                for(Account duplicte: AccountWithEmail){
                   
                    duplicte.Email__c = duplicte.Id+'.' + duplicte.Email__c;
                    update duplicte;
                }
            }
        }
    
    }
global void finish(Database.BatchableContext bc){}
    
}

First variant of a test

@IsTest
public class EmailDuplicatetorTest {
    @IsTest static void DuplicatedEmaialTest(){
        list<Account> DuplicatedEmails = new list<Account>();
        for (integer i=0;i<10;i++){
        DuplicatedEmails.add(new Account(LastName = 'LastName'+i, Email__c = 'duplicate@duplicate.com'));

        }
        insert DuplicatedEmails;
        system.debug(DuplicatedEmails);
        
        string Email;
        string ExpectedEmail;
 
        EmailDuplicatorcontroller edc = new EmailDuplicatorcontroller();
        Id batchId = Database.executeBatch(edc);

        for (Account a : DuplicatedEmails){
    system.debug(a);
            list<Account> ChangedAcc = new list<Account>([SELECT Email__c FROM Account WHERE id=:a.Id]);
            Email = ChangedAcc[0].Email__c;
    system.debug(Email);
            ExpectedEmail = a.ID+'.'+'duplicate@duplicate.com';
            boolean result = (Email.equals(ExpectedEmail.TolowerCase())); 
     system.debug(result);
            
            system.assertEquals(result, True);
        }
    }  
}

Second variant

@IsTest
public class EmailDuplicatetorTest {
    @testSetup static void setup(){
 
        for (integer i=0;i<10;i++){
        insert new Account(Name = 'LastName'+i,Email__c = 'duplicate@duplicate.com');
     
        }
    }
    @IsTest static void DuplicatedEmaialTest(){

        EmailDuplicatorcontroller edc = new EmailDuplicatorcontroller();
        DataBase.executeBatch(edc);
        
        string Email;
        string ExpectedEmail;
        for (integer i=0;i<10;i++){
            list<Account> ChangedAcc = new list<Account>([SELECT ID, Email__c FROM Account WHERE Name =: 'LastName'+i]);
            system.debug(ChangedAcc);
            Email = ChangedAcc[0].Email__c;
    system.debug(Email);
            ExpectedEmail = ChangedAcc[0].Id +'.'+'duplicate@duplicate.com';
            boolean result = (Email.equals(ExpectedEmail.TolowerCase())); 
     system.debug(result);

            system.assertEquals(result, True);
        }
    }  
}

Also I have log like this. If all correct it should be "{$ID}.duplicate@duplicate.com" True [20] - This is Changed Account, The Email should be changed [22] - Email of this Acc [25] - comparison between Actual Email and Email that presumably should be.

enter image description here

3
  • 1
    You need to use Test.startTest/Test.stopTest around all async operations like Database.executeBatch for these to be run. See the documentation.
    – Phil W
    May 26 at 12:16
  • 1
    You should note that your code does not "run fine." It will fail if your queries exceed 100 records. DML in a loop is bad, as is SOQL in a loop. See this. Also, new List(query) is unnecessary. Finally, you should add Test.startTest at beginning of @TestSetup to avoid using unit test governor limits.
    – sfdcfox
    May 26 at 12:37
  • A final other point; you don't need to use global for your batch class's access modifier unless you want the batch to be accessible across a namespace boundary, and either way you don't need the methods to be global.
    – Phil W
    May 26 at 12:38

1 Answer 1

4

The key thing that you're missing is Test.startTest() and Test.stopTest().

Async apex (@future, queueable, batch, schedulable) runs in a separate transaction from the one that it's called from. In the case of a test, that means that it would run after the completion of the test (not very useful when the async code is the code you're trying to test).

So you call Test.startTest()
Then call the code that would run async (either directly or indirectly)
Then call Test.stopTest()

When Test.stopTest() is called, it causes any async code in the pipeline to be run synchronously. It only works for async code put into the pipeline after startTest() was called, and it only works for one level of async code (so if your async code itself causes another async transaction, it'll either cause your test to fail or that second round of async code to not be executed for the test).

The other way around this is to call the async code directly (i.e. without using Database.executeBatch(), System.enqueueJob(), etc...). With the exception of @future methods, they are methods just like any other class method. If you provide the appropriate arguments and set the environment up correctly, they'll run all the same.

You can call edc.execute(null, [SELECT Email__c FROM Account WHERE Email__c != null AND Email__c !='gdpr@gdpr.com']);. The batchableContext is generally not something that people like you or I will use, so it's fine to just pass a null through in most cases.

1

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.