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
Patrick Marks 2Patrick Marks 2 

Help with batch/scheduled apex

Hey everyone-I'm trying to figure out the best way to transfer Account ownership of certain accounts once a day. I would use declarative tools but can't because the data that would fire the automation is coming from a couple of custom formula fields. I'm confused whether I need to write a scheduled class, a batch class or a scheduled batch class. I've gone through trailhead and sfdc help docs and am still unclear on how to accomplish this. Any pointers on how to get started are much appreciated.
Raj VakatiRaj Vakati
This is what i can able to suggests  .. Please go with the batch apex


Reason is 
  1. You can able to run the batch job once on large amount data 
  2. If you want to re-run the same code based on the schedule you can able to call the batch apex from the schedule .. 
So with this approach, you are writing the code that will be used for one time as well scheduled 
Patrick Marks 2Patrick Marks 2
Thanks for the reply, Raj. Just to be clear-going with batch apex would require me to write one batchable class and one schedulable class, correct?
Raj VakatiRaj Vakati
You have both of them in a single class also ..   but its recommended to keep two classes  
Patrick Marks 2Patrick Marks 2
Hi Raj-thanks for the help. I was able to write the schedulable class and test class but am only getting 42% code coverage on my test class. Can you help me understand why? Here's the schedulable class:
global without sharing class UnassignAccountsDueToInactivity implements Schedulable {
    global void execute(SchedulableContext ctx) {
        List<Account> acctList = [SELECT Id, Name, OwnerId 
                                  FROM Account 
                                  WHERE (OwnerProfileName__c LIKE '%PM AE%' OR OwnerProfileName__c LIKE '%PM SDR%') AND 
                                  Current_Customer__c = NULL AND
                                  Number_of_Open_Opportunities__c <= 0 AND
                                  DaysSinceLastActivity__c >= 45 AND
                                  DaysSinceLastOwnerChangeDate__c >= 14];
        
        if(!acctList.isEmpty()){
            for(Account acc : acctList){
                acc.DisqualifiedReason__c = 'No response';
                acc.OwnerId = '005f4000000vKnPAAU';
            }
            update acctList;
        }
    }
}

And here is my test class:
@isTest
public class UnassignAccountsDueToInactivityTest {
    public static testMethod void myTestMethod() {        
        Test.startTest();
        Account acct = new Account();
        acct.Name = 'Test';
        acct.Type = 'Corporation';
        acct.Entity_Type__c = 'C-Corp';
        acct.Current_Customer__c = NULL;
        insert acct;
        
        // This test runs a scheduled job at midnight Sept. 3rd. 2022
        String CRON_EXP = '0 0 0 3 9 ? 2022';
        // Schedule the test job
        String jobId = System.schedule('UnassignAccountsDueToInactivity', CRON_EXP, new UnassignAccountsDueToInactivity());
        // Get the information from the CronTrigger API object 
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId]; 
        // Verify the expressions are the same System.assertEquals(CRON_EXP, ct.CronExpression); 
        // Verify the job has not run 
        System.assertEquals(0, ct.TimesTriggered); 
        // Verify the next time the job will run 
        System.assertEquals('2022-09-03 00:00:00', String.valueOf(ct.NextFireTime));   
        Test.stopTest();        
    }
}

I appreciate the help!
Raj VakatiRaj Vakati
Update class as below
global without sharing class UnassignAccountsDueToInactivity implements Schedulable {
	
	
    global void execute(SchedulableContext ctx) {
		
		User uId = [Select Id from User Where Email='YOUROWNERIDUSERNAME@email.com' Limit 1 ] ;
        List<Account> acctList = [SELECT Id, Name, OwnerId 
                                  FROM Account 
                                  WHERE (OwnerProfileName__c LIKE '%PM AE%' OR OwnerProfileName__c LIKE '%PM SDR%') AND 
                                  Current_Customer__c = NULL AND
                                  Number_of_Open_Opportunities__c <= 0 AND
                                  DaysSinceLastActivity__c >= 45 AND
                                  DaysSinceLastOwnerChangeDate__c >= 14];
        
        if(!acctList.isEmpty()){
            for(Account acc : acctList){
                acc.DisqualifiedReason__c = 'No response';
                acc.OwnerId = uId.Id;
            }
            update acctList;
        }
    }
}

Test class
@isTest
public class UnassignAccountsDueToInactivityTest {
    public static testMethod void myTestMethod() {  

 Profile pf= [Select Id from profile where Name LIKE '%PM AE%' Limit 1]; 
        
        String orgId=UserInfo.getOrganizationId(); 
        String dateString=String.valueof(Datetime.now()).replace(' ','').replace(':','').replace('-','') ;
        Integer RandomId=Integer.valueOf(Math.rint(Math.random()*1000000)); 
        String uniqueName=orgId+dateString+RandomId; 
        User uu=new User(firstname = 'ABC', 
                         lastName = 'XYZ', 
                         email = uniqueName + '@test' + orgId + '.org', 
                         Username = uniqueName + '@test' + orgId + '.org', 
                         EmailEncodingKey = 'ISO-8859-1', 
                         Alias = uniqueName.substring(18, 23), 
                         TimeZoneSidKey = 'America/Los_Angeles', 
                         LocaleSidKey = 'en_US', 
                         LanguageLocaleKey = 'en_US', 
                         ProfileId = pf.Id
                        ); 
        
        
        insert uu;
	System.runAs(uu){		
        Test.startTest();
        Account acct = new Account();
        acct.Name = 'Test';
        acct.Type = 'Corporation';
        acct.Entity_Type__c = 'C-Corp';
        acct.Current_Customer__c = NULL;
		// set DaysSinceLastActivity__c & DaysSinceLastOwnerChangeDate__c values to meet the SOQL where clause 
        insert acct;
        
        // This test runs a scheduled job at midnight Sept. 3rd. 2022
        String CRON_EXP = '0 0 0 3 9 ? 2022';
        // Schedule the test job
        String jobId = System.schedule('UnassignAccountsDueToInactivity', CRON_EXP, new UnassignAccountsDueToInactivity());
        // Get the information from the CronTrigger API object 
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId]; 
        // Verify the expressions are the same System.assertEquals(CRON_EXP, ct.CronExpression); 
        // Verify the job has not run 
        System.assertEquals(0, ct.TimesTriggered); 
        // Verify the next time the job will run 
        System.assertEquals('2022-09-03 00:00:00', String.valueOf(ct.NextFireTime));   
        Test.stopTest();    	
	}		
    }
}

​​​​​​​
Patrick Marks 2Patrick Marks 2
Thanks for the input, Raj. However, when I run the test class for your class, I only get 25% code coverage. Any thoughts?
Raj VakatiRaj Vakati
Which lines are not covering 
Patrick Marks 2Patrick Marks 2
It's lines 7, 15-18 and 20 (see screenshot attached).

User-added image
 
Patrick Marks 2Patrick Marks 2
Actually, I just replaced 'YOUROWNERIDUSERNAME@email.com' from line 6 with the correct user's email and it bumped it up to 50%. But still no coverage for lines 16, 17, 18 and 20.
Raj VakatiRaj Vakati
You need to set the data meet where condition like Number_of_Open_Opportunities__c  >14 and etc.. if its formula fields insert data to calculate the formula fields 
Patrick Marks 2Patrick Marks 2
I just updated my test class to set the data for DaysSinceLastActivity__c and DaysSinceLastOwnerChangeDate__c and still only getting 50% (no coverage on lines 16, 17, 18 and 20). Here's the updated test class:
@isTest
public class UnassignAccountsDueToInactivityTest {
    public static testMethod void myTestMethod() {  

 Profile pf= [Select Id from profile where Name LIKE '%PM AE%' Limit 1]; 
        
        String orgId=UserInfo.getOrganizationId(); 
        String dateString=String.valueof(Datetime.now()).replace(' ','').replace(':','').replace('-','') ;
        Integer RandomId=Integer.valueOf(Math.rint(Math.random()*1000000)); 
        String uniqueName=orgId+dateString+RandomId; 
        User uu=new User(firstname = 'ABC', 
                         lastName = 'XYZ', 
                         email = uniqueName + '@test' + orgId + '.org', 
                         Username = uniqueName + '@test' + orgId + '.org', 
                         EmailEncodingKey = 'ISO-8859-1', 
                         Alias = uniqueName.substring(18, 23), 
                         TimeZoneSidKey = 'America/Los_Angeles', 
                         LocaleSidKey = 'en_US', 
                         LanguageLocaleKey = 'en_US', 
                         ProfileId = pf.Id
                        ); 
        
        insert uu;
	System.runAs(uu){		
        Test.startTest();
        Account acct = new Account();
        acct.Name = 'Test';
        acct.OwnerId = '005f4000003KbfZAAS';
        acct.Type = 'Corporation';
        acct.Entity_Type__c = 'C-Corp';
        acct.Current_Customer__c = NULL;
        acct.LastOwnershipChangeDate__c = Date.newInstance(2018, 3, 3);
		// set DaysSinceLastActivity__c & DaysSinceLastOwnerChangeDate__c values to meet the SOQL where clause
        insert acct;
        
        Task t = new Task();
        t.Subject = 'Test';
        t.OwnerId = '005f4000003KbfZAAS';
        t.WhoId = '0031g00000JaFl8';
        t.WhatId = acct.Id;
        t.CreatedDate = Date.newInstance(2018, 3, 3);
        insert t;
        
        // This test runs a scheduled job at midnight Sept. 3rd. 2022
        String CRON_EXP = '0 0 0 3 9 ? 2022';
        // Schedule the test job
        String jobId = System.schedule('UnassignAccountsDueToInactivity', CRON_EXP, new UnassignAccountsDueToInactivity());
        // Get the information from the CronTrigger API object 
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId]; 
        // Verify the expressions are the same System.assertEquals(CRON_EXP, ct.CronExpression); 
        // Verify the job has not run 
        System.assertEquals(0, ct.TimesTriggered); 
        // Verify the next time the job will run 
        System.assertEquals('2022-09-03 00:00:00', String.valueOf(ct.NextFireTime));   
        Test.stopTest();    	
	}		
    }
}

 
Patrick Marks 2Patrick Marks 2
Actually, just figured it out. It was because I was using Created Date instead of Activity Date on my tasks:
@isTest
public class UnassignAccountsDueToInactivityTest {
    public static testMethod void myTestMethod() {  
        
        Test.startTest();
        
        Profile pf= [Select Id from profile where Name LIKE '%PM AE%' Limit 1]; 
        
        String orgId=UserInfo.getOrganizationId(); 
        String dateString=String.valueof(Datetime.now()).replace(' ','').replace(':','').replace('-','') ;
        Integer RandomId=Integer.valueOf(Math.rint(Math.random()*1000000)); 
        String uniqueName=orgId+dateString+RandomId; 
        User uu=new User(firstname = 'ABC', 
                         lastName = 'XYZ', 
                         email = uniqueName + '@test' + orgId + '.org', 
                         Username = uniqueName + '@test' + orgId + '.org', 
                         EmailEncodingKey = 'ISO-8859-1', 
                         Alias = uniqueName.substring(18, 23), 
                         TimeZoneSidKey = 'America/Los_Angeles', 
                         LocaleSidKey = 'en_US', 
                         LanguageLocaleKey = 'en_US', 
                         ProfileId = pf.Id
                        ); 
        
        insert uu;
        System.runAs(uu){		
            Account acct = new Account();
            acct.Name = 'Test';
            acct.OwnerId = '005f4000003KbfZAAS';
            acct.Type = 'Corporation';
            acct.Entity_Type__c = 'C-Corp';
            acct.Current_Customer__c = NULL;
            acct.LastOwnershipChangeDate__c = Date.newInstance(2018, 3, 3);
            insert acct;
            
            Account acct2 = new Account();
            acct2.Name = 'Test2';
            acct2.OwnerId = '005f4000003KbfZAAS';
            acct2.Type = 'Corporation';
            acct2.Entity_Type__c = 'C-Corp';
            acct2.Current_Customer__c = NULL;
            acct2.LastOwnershipChangeDate__c = Date.newInstance(2018, 3, 3);
            insert acct2;
            
            Contact con = new Contact();
            con.FirstName = 'test';
            con.LastName = 'testy';
            con.AccountId = acct.Id;
            con.LeadSource = 'Blog Post';
            insert con;
            
            Contact con2 = new Contact();
            con2.FirstName = 'derp';
            con2.LastName = 'derpy';
            con2.AccountId = acct2.Id;
            con2.LeadSource = 'Blog Post';
            insert con2;
            
            Task t = new Task();
            t.Subject = 'Test';
            t.OwnerId = '005f4000003KbfZAAS';
            t.WhoId = con.Id;
            t.WhatId = acct.Id;
            t.ActivityDate = Date.newInstance(2018, 3, 3);
            t.Status = 'Completed';
            insert t;
            
            Task t2 = new Task();
            t2.Subject = 'Test';
            t2.OwnerId = '005f4000003KbfZAAS';
            t2.WhoId = con2.Id;
            t2.WhatId = acct2.Id;
            t2.ActivityDate = Date.newInstance(2018, 3, 3);
            t2.Status = 'Completed';
            insert t2;
            
            // Schedule the test job
            
            String jobId = System.schedule('UnassignAccountsDueToInactivity',
                                           UnassignAccountsDueToInactivity.CRON_EXP, 
                                           new UnassignAccountsDueToInactivity());
            
            // Get the information from the CronTrigger API object
            CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
                              NextFireTime
                              FROM CronTrigger WHERE id = :jobId];
            
            // Verify the expressions are the same
            System.assertEquals(UnassignAccountsDueToInactivity.CRON_EXP, 
                                ct.CronExpression);
            
            // Verify the job has not run
            System.assertEquals(0, ct.TimesTriggered);
            
            // Verify the next time the job will run
            System.assertEquals('2022-09-03 00:00:00', 
                                String.valueOf(ct.NextFireTime));
            System.assertNotEquals('testScheduledApexFromTestMethodUpdated',
                                   [SELECT id, name FROM account WHERE id = :acct.id].name);
            
            Test.stopTest();
        }
    }
}

 
Raj VakatiRaj Vakati
Cool .. Close this thread