• jkucera3
  • NEWBIE
  • 35 Points
  • Member since 2010

  • Chatter
    Feed
  • 1
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 2
    Questions
  • 1
    Replies

There seems to be nothing in the Apex guides on how to test a set controller.  This method runs when a button is clicked on a custom object list view and processes the records selected from the list view.

 

How do I test it?  I can't figure out how to get any records "selected" in the StandardSetController.

 

 

public with sharing class replacerRunAllSelectedRulesController{

    ApexPages.StandardSetController setCon;
    private PageReference savePage;

    public replacerRunAllSelectedRulesController(ApexPages.StandardController controller) {
        //Not sure why this is needed...
    }//replacerRunAllSelectedRulesController

    public replacerRunAllSelectedRulesController(ApexPages.StandardSetController controller) {
        setCon=controller;
        List<Replacer__c> selectedRules=new List<Replacer__c>();
    }//replacerRunAllSelectedRulesController
    

    public PageReference runSelectedRules(){
        List<Replacer__c> selectedRules=new List<Replacer__c>();
        for(sObject s: setCon.getSelected()){
            Replacer__c r=(Replacer__c)s;
            selectedRules.add(r);
        }//for 1
        String objectName='';
        Boolean sendEmail=TRUE;
        
        if(selectedRules.size()==0){
        	ApexPages.addmessage(new ApexPages.message(ApexPages.severity.ERROR, 
        	'You must select at least one rule.  Please go back and select a rule by checking the checkbox and try again.'));        
        }else{
	        
	        Integer numJobsCantBeRunNow=replacerExecute.replacerQueueBatchJobs(objectName, selectedRules, sendEmail);
	        if(numJobsCantBeRunNow==0){
	            ApexPages.Message myMsg = new ApexPages.Message(ApexPages.Severity.CONFIRM,'Replacement has begun.  This can take ~10-30 minutes for each 10,000 records.  You will receive 1 email confirmation for each different object as all rules for one object are run together in one job.');
	            ApexPages.addMessage(myMsg);
	        }else{
	            //else throw an error that they need to select fewer rules to run right now
	            ApexPages.addmessage(new ApexPages.message(ApexPages.severity.ERROR, 'The selected rules cannot be run now as they would exceed your companies batch job limit of 5.  One job is created for each different object.  Please unselect ' + numJobsCantBeRunNow + ' object\'s rules and try again.  To view the batch queue, click Setup-->Monitoring-->Apex Jobs'));
	        }//if 2
        }//if 1
        
        return ApexPages.CurrentPage();
    }//runSelectedRules
    
}//replacerRunAllSelectedRulesController

 

Here's where I'm at - I have the controller assigned, but can't figure out how to select the records in the set controller:

 

 

public static testMethod void verifyListViewRunSelectedRulesButton() {
	Boolean deleteReplacers=TRUE;
        Boolean deleteQueuedJobs=FALSE;
        Boolean deleteFieldList=FALSE;
        Boolean deleteTestData=TRUE;
        Boolean deleteJobHistory=FALSE;
        Boolean abortBatchJobs=FALSE;
        //There should be zero replacers after this method runs
        deleteTestData(deleteReplacers, deleteQueuedJobs, deleteFieldList, deleteTestData, deleteJobHistory, abortBatchJobs);

        //Make 1 active rule so that a preview can be shown
        Boolean rActive=TRUE;
        String rObjectName='test__c';
        String rFieldName='picklist__c';
        String rFind='a';
        String rValue='b';
        List<replacer__c> rules=new List<replacer__c>();
        rules.add(createRule(rActive, rObjectName, rFieldName, rFind, rValue));
        rFieldName='text__c';
        rules.add(createRule(rActive, rObjectName, rFieldName, rFind, rValue));
        insert rules;

        Long numRecords=2;
        Date dat=date.today();
        DateTime datTim=dateTime.now();
        Decimal dec=0.123;
        String em='a@a.com';
        String ph='415-555-1234';
        String pick='a';            
        String text='a';
        String ur='http://www.google.com';

        //Insert some test data so we can play with the controller
        List<test__c> ts=createTestRecords(numRecords, dat, datTim, dec, em, ph, pick, text, ur); 
        pick='c';
        List<test__c> ts2=createTestRecords(1, dat, datTim, dec, em, ph, pick, text, ur);
        
        ApexPages.StandardSetController sc = new ApexPages.StandardSetController(rules);
        replacerRunAllSelectedRulesController scExt = new replacerRunAllSelectedRulesController(sc);
	//How do I select both "rules"?	
        test.StartTest();
            scExt.runSelectedRules();
        test.stopTest();

	List<test__c> results=[SELECT Id, picklist__c FROM test__c WHERE Id IN: ts];
        for(test__c t: results){
            system.assertEquals(rValue, t.picklist__c);
        }//for

        List<test__c> results2=[SELECT Id, picklist__c FROM test__c WHERE Id IN: ts2];
        for(test__c t: results2){
            system.assertEquals(pick, t.picklist__c);
        }//for
        
    }//verifyListViewRunSelectedRulesButton

 

 

 

 

 

Nothing changed with my test - it only calls one batch job.  This worked 2wks ago, but now is failing, preventing me from uploading an important bug fix to my customers.

 

22:56:34.835|EXCEPTION_THROWN|[EXTERNAL]|System.UnexpectedException: No more than one executeBatch can be called from within a testmethod. Please make sure the iterable returned from your start method matches the batch size, resulting in one executeBatch invocation.

 

My only change was an addition of a 3rd test that calls the same method "TryBatchJobsAgain", which passes, despite this one failing.  Perhaps the test doesn't truly finish the batch job before the other test starts?

 

When I comment out this test, an unrelated test fails that calls a separate batch job.

 

When I move that test to a completely separate test class, it still fails.  

 

Test Method:

 

public static testMethod void testRemoveDelayBatchJobFromQueue() {
        Long numRecordsToCreate=2;
        Long numUsersToCreate=2;
        String profileName='System Administrator';

        Boolean Active=True;
        Double URDaysDelay=2;
        String URObjectName='UnfollowTest__c';
        String URFieldName='String__c';
        String UROperator='equals';
        String URValue='a';//testing capitalization as well
        List<UnfollowRule__c> urs=new List<UnfollowRule__c>();


        String Str='a';
        Boolean Check=TRUE;
        String Pick='2';
        Double Dec=1000;
        Date Dat=date.today();
        DateTime DatTim=dateTime.now();
        String Phone = '415-555-5555';
        String Email = 'test@test.com';
        String Url='www.test.com';
          
        cleanUpTestData();
        
        urs.add(createUR(Active, URObjectName, URFieldName, UROperator, URValue, URDaysDelay ));    
        insert urs;
        
        List<Id> recordIds=createUnfollowTestRecords(numRecordsToCreate,Check, Dat, DatTim, Dec, Email, Phone, Pick, Str, Url);
        List<User> users=createUsers(numUsersToCreate, profileName);
        List<EntitySubscription> subs=createSubs(users,recordIds);
        List<UnfollowQueue__c> uqs=new List<UnfollowQueue__c>();
        for (Id i:recordIds){
            UnfollowQueue__c uq=new UnfollowQueue__c();
            uq.recordId__c=i;
            uq.CriteriaMetDate__c=Date.Today()-URDaysDelay.intValue();
            uq.DaysDelay__c=URDaysDelay;
            uqs.add(uq);
        }//for 1

        List<Id> recordIds2=createUnfollowTestRecords(numRecordsToCreate,Check, Dat, DatTim, Dec, Email, Phone, Pick, Str, Url);
        List<EntitySubscription> subs2=createSubs(users,recordIds2);

        for (Id i:recordIds2){
            UnfollowQueue__c uq=new UnfollowQueue__c();
            uq.recordId__c=i;
            uq.CriteriaMetDate__c=Date.Today()-URDaysDelay.intValue()+1;
            uq.DaysDelay__c=URDaysDelay;
            uqs.add(uq);
        }//for 1

        insert uqs;
        
        Boolean delayJob = TRUE;
        Boolean delayRulesIncluded=FALSE;
        Boolean evalateEachRecordForDaysDelay=FALSE;
        Boolean addFieldNames=TRUE;
        String sObjectQuery = 'Select Id, chttrunfollow__recordId__c FROM chttrunfollow__UnfollowQueue__c WHERE chttrunfollow__scheduledUnfollowDate__c<= TODAY AND IsDeleted=FALSE';
        UnfollowBatchJobsQueue__c job=new UnfollowBatchJobsQueue__c(delayJob__c=delayJob, delayRulesIncluded__c=delayRulesIncluded, evalateEachRecordForDaysDelay__c=evalateEachRecordForDaysDelay, objectName__c=URobjectName, numRulesUsedInThisObject__c=urs.size(), sObjectQuery__c=sObjectQuery);
        insert job;
        
        system.debug('Unfollow Queue Size: '+[SELECT Id FROM UnfollowBatchJobsQueue__c].size());
        
        test.StartTest();
            unfollowTryBatchJobsAgain.unfollowTryBatchJobsAgain();//This is a regular class that calls 1 batch job        test.stopTest();
        
        //first confirm the job was removed from the queue
        List<UnfollowBatchJobsQueue__c> bjQueue = [SELECT Id FROM UnfollowBatchJobsQueue__c];
        system.AssertEquals(0,bjQueue.size());
        //then confirm it actually worked
        List<EntitySubscription> es=[Select Id FROM EntitySubscription WHERE ParentId IN :recordIds];
        system.assertEquals(0,es.size());
        uqs=[SELECT ID FROM UnfollowQueue__c WHERE ScheduledUnfollowDate__c<=TODAY];
        system.assertEquals(0,uqs.size());
        es=[Select Id FROM EntitySubscription WHERE ParentId IN:recordIds2];
        system.assertEquals(numRecordsToCreate*numUsersToCreate,es.size());
    }//testRemoveDelayBatchJobFromQueue

 

 

Note the highlighted red below is the ONLY batch job executed from this test - even the system logs confirm this.

 

global with sharing class unfollowTryBatchJobsAgain{

    public static void unfollowTryBatchJobsAgain(){
        Integer numBatchApexJobsLimit=5;//at time of coding, there are at most 5 concurrent batch apex jobs in any org
        List<AsyncApexJob> numBatchJobs = [SELECT Id, Status FROM AsyncApexJob WHERE Status = 'Queued' OR Status = 'Processing'];

        //This is the number of jobs that can be queued up by this method
        Integer numJobsAvailable=numBatchApexJobsLimit - numBatchJobs.size();
	
        if(numJobsAvailable>0){
            List<UnfollowBatchJobsQueue__c> batchJobsQueued=[SELECT Id, IsDeleted, delayJob__c, delayRulesIncluded__c, evalateEachRecordForDaysDelay__c, numRulesUsedInThisObject__c, objectName__c, sObjectQuery__c FROM UnfollowBatchJobsQueue__c WHERE IsDeleted=FALSE ORDER BY  CreatedDate ASC];
            //Goal here is to process the delay queue first as it's more important than the others. Rather than do 2 queries, it's handled with variables here:
            Integer delayJobNum=1000;//initialize to huge number as a backup
            for (Integer i=0;i<batchJobsQueued.size();i++){
                if (batchJobsQueued[i].delayJob__c==TRUE){
                    delayJobNum=i;
                    break;
                }//if 2
            }//for 1
    		List<UnfollowBatchJobsQueue__c> batchJobsToDelete=new List<UnfollowBatchJobsQueue__c>();        
            for(Integer i=0; i<numJobsAvailable && i<batchJobsQueued.size(); i++){
                //if this is the high priority "delayed records scheduled for unfollow today" job, do it first
                if (delayJobNum!=1000){
                    UnfollowProcessUnfollowQueueBatch unfollowDelayedRecords= new UnfollowProcessUnfollowQueueBatch();
                    unfollowDelayedRecords.sObjectQuery=batchJobsQueued[delayJobNum].sObjectQuery__c;                    try{
                        Id unfollowRulesProcessId = Database.executeBatch(unfollowDelayedRecords, 200); 
                        batchJobsToDelete.add( batchJobsQueued[delayJobNum]);
                    } catch(exception e){
//                        system.debug('Either the batch failed or the job deletion from teh queue failed: '+e);
                    }//try
                } else if(batchJobsQueued[i].delayRulesIncluded__c==FALSE){
                 //is this the simple case with no "days delay" rules?
                    UnfollowRecordsBatch  unfollowRecords= new UnfollowRecordsBatch();
                    unfollowRecords.ObjectName=batchJobsQueued[i].objectName__c;
                    unfollowRecords.numRulesUsedInThisObject=batchJobsQueued[i].numRulesUsedInThisObject__c.intValue();
                    unfollowRecords.sObjectQuery =  batchJobsQueued[i].sObjectQuery__c;
                
                    try{
                        Id unfollowRulesProcessId = Database.executeBatch(unfollowRecords, 200); 
                        batchJobsToDelete.add(batchJobsQueued[i]);
                    } catch(exception e){
//                        system.debug('Either the batch failed or the job deletion from the queue failed: '+e);
                    }//try
                } else {
                //else it's the more complex case where we need to check for the unfollow date
                    UnfollowQueueDelayRecordsBatch queueDelayRecords= new UnfollowQueueDelayRecordsBatch();
                    queueDelayRecords.ObjectName=batchJobsQueued[i].objectName__c;
                    queueDelayRecords.sObjectQuery =  batchJobsQueued[i].sObjectQuery__c;
                    queueDelayRecords.evalateEachRecordForDaysDelay=batchJobsQueued[i].evalateEachRecordForDaysDelay__c;
                    
//let's cross our fingers that the rule criteria didn't change between when this job first ran and now :(  
//Will the code fail elegantly if the rules were changed?
//I'd rather not create a 3rd queue just to save the state of the rules due to stupid batch apex limits
                    queueDelayRecords.delayRules=[Select Id, ObjectName__c, Active__c, FieldName__c, FieldType__c, Operator__c, Value__c, DaysDelay__c FROM UnfollowRule__c WHERE DaysDelay__c>0 AND Active__c = TRUE AND objectName__c=:queueDelayRecords.ObjectName]; 

                    try{
                        Id unfollowRulesProcessId = Database.executeBatch(queueDelayRecords, 200); 
                        batchJobsToDelete.add( batchJobsQueued[i]);
                    } catch(exception e){
//                        system.debug('Either the batch failed or the job deletion from the queue failed: '+e);
                    }//try
                    
                }//if 2
            }//for 1
            try{
            	delete batchJobsToDelete;
            } catch(exception e){
//                        system.debug('job deletion from the queue failed: '+e);            
            }//try delete

        }//if 1


    }//unfollowTryBatchJobsAgain
}//unfollowTryBatchJobsAgain

 

:

 

 

 

 

 

Nothing changed with my test - it only calls one batch job.  This worked 2wks ago, but now is failing, preventing me from uploading an important bug fix to my customers.

 

22:56:34.835|EXCEPTION_THROWN|[EXTERNAL]|System.UnexpectedException: No more than one executeBatch can be called from within a testmethod. Please make sure the iterable returned from your start method matches the batch size, resulting in one executeBatch invocation.

 

My only change was an addition of a 3rd test that calls the same method "TryBatchJobsAgain", which passes, despite this one failing.  Perhaps the test doesn't truly finish the batch job before the other test starts?

 

When I comment out this test, an unrelated test fails that calls a separate batch job.

 

When I move that test to a completely separate test class, it still fails.  

 

Test Method:

 

public static testMethod void testRemoveDelayBatchJobFromQueue() {
        Long numRecordsToCreate=2;
        Long numUsersToCreate=2;
        String profileName='System Administrator';

        Boolean Active=True;
        Double URDaysDelay=2;
        String URObjectName='UnfollowTest__c';
        String URFieldName='String__c';
        String UROperator='equals';
        String URValue='a';//testing capitalization as well
        List<UnfollowRule__c> urs=new List<UnfollowRule__c>();


        String Str='a';
        Boolean Check=TRUE;
        String Pick='2';
        Double Dec=1000;
        Date Dat=date.today();
        DateTime DatTim=dateTime.now();
        String Phone = '415-555-5555';
        String Email = 'test@test.com';
        String Url='www.test.com';
          
        cleanUpTestData();
        
        urs.add(createUR(Active, URObjectName, URFieldName, UROperator, URValue, URDaysDelay ));    
        insert urs;
        
        List<Id> recordIds=createUnfollowTestRecords(numRecordsToCreate,Check, Dat, DatTim, Dec, Email, Phone, Pick, Str, Url);
        List<User> users=createUsers(numUsersToCreate, profileName);
        List<EntitySubscription> subs=createSubs(users,recordIds);
        List<UnfollowQueue__c> uqs=new List<UnfollowQueue__c>();
        for (Id i:recordIds){
            UnfollowQueue__c uq=new UnfollowQueue__c();
            uq.recordId__c=i;
            uq.CriteriaMetDate__c=Date.Today()-URDaysDelay.intValue();
            uq.DaysDelay__c=URDaysDelay;
            uqs.add(uq);
        }//for 1

        List<Id> recordIds2=createUnfollowTestRecords(numRecordsToCreate,Check, Dat, DatTim, Dec, Email, Phone, Pick, Str, Url);
        List<EntitySubscription> subs2=createSubs(users,recordIds2);

        for (Id i:recordIds2){
            UnfollowQueue__c uq=new UnfollowQueue__c();
            uq.recordId__c=i;
            uq.CriteriaMetDate__c=Date.Today()-URDaysDelay.intValue()+1;
            uq.DaysDelay__c=URDaysDelay;
            uqs.add(uq);
        }//for 1

        insert uqs;
        
        Boolean delayJob = TRUE;
        Boolean delayRulesIncluded=FALSE;
        Boolean evalateEachRecordForDaysDelay=FALSE;
        Boolean addFieldNames=TRUE;
        String sObjectQuery = 'Select Id, chttrunfollow__recordId__c FROM chttrunfollow__UnfollowQueue__c WHERE chttrunfollow__scheduledUnfollowDate__c<= TODAY AND IsDeleted=FALSE';
        UnfollowBatchJobsQueue__c job=new UnfollowBatchJobsQueue__c(delayJob__c=delayJob, delayRulesIncluded__c=delayRulesIncluded, evalateEachRecordForDaysDelay__c=evalateEachRecordForDaysDelay, objectName__c=URobjectName, numRulesUsedInThisObject__c=urs.size(), sObjectQuery__c=sObjectQuery);
        insert job;
        
        system.debug('Unfollow Queue Size: '+[SELECT Id FROM UnfollowBatchJobsQueue__c].size());
        
        test.StartTest();
            unfollowTryBatchJobsAgain.unfollowTryBatchJobsAgain();//This is a regular class that calls 1 batch job        test.stopTest();
        
        //first confirm the job was removed from the queue
        List<UnfollowBatchJobsQueue__c> bjQueue = [SELECT Id FROM UnfollowBatchJobsQueue__c];
        system.AssertEquals(0,bjQueue.size());
        //then confirm it actually worked
        List<EntitySubscription> es=[Select Id FROM EntitySubscription WHERE ParentId IN :recordIds];
        system.assertEquals(0,es.size());
        uqs=[SELECT ID FROM UnfollowQueue__c WHERE ScheduledUnfollowDate__c<=TODAY];
        system.assertEquals(0,uqs.size());
        es=[Select Id FROM EntitySubscription WHERE ParentId IN:recordIds2];
        system.assertEquals(numRecordsToCreate*numUsersToCreate,es.size());
    }//testRemoveDelayBatchJobFromQueue

 

 

Note the highlighted red below is the ONLY batch job executed from this test - even the system logs confirm this.

 

global with sharing class unfollowTryBatchJobsAgain{

    public static void unfollowTryBatchJobsAgain(){
        Integer numBatchApexJobsLimit=5;//at time of coding, there are at most 5 concurrent batch apex jobs in any org
        List<AsyncApexJob> numBatchJobs = [SELECT Id, Status FROM AsyncApexJob WHERE Status = 'Queued' OR Status = 'Processing'];

        //This is the number of jobs that can be queued up by this method
        Integer numJobsAvailable=numBatchApexJobsLimit - numBatchJobs.size();
	
        if(numJobsAvailable>0){
            List<UnfollowBatchJobsQueue__c> batchJobsQueued=[SELECT Id, IsDeleted, delayJob__c, delayRulesIncluded__c, evalateEachRecordForDaysDelay__c, numRulesUsedInThisObject__c, objectName__c, sObjectQuery__c FROM UnfollowBatchJobsQueue__c WHERE IsDeleted=FALSE ORDER BY  CreatedDate ASC];
            //Goal here is to process the delay queue first as it's more important than the others. Rather than do 2 queries, it's handled with variables here:
            Integer delayJobNum=1000;//initialize to huge number as a backup
            for (Integer i=0;i<batchJobsQueued.size();i++){
                if (batchJobsQueued[i].delayJob__c==TRUE){
                    delayJobNum=i;
                    break;
                }//if 2
            }//for 1
    		List<UnfollowBatchJobsQueue__c> batchJobsToDelete=new List<UnfollowBatchJobsQueue__c>();        
            for(Integer i=0; i<numJobsAvailable && i<batchJobsQueued.size(); i++){
                //if this is the high priority "delayed records scheduled for unfollow today" job, do it first
                if (delayJobNum!=1000){
                    UnfollowProcessUnfollowQueueBatch unfollowDelayedRecords= new UnfollowProcessUnfollowQueueBatch();
                    unfollowDelayedRecords.sObjectQuery=batchJobsQueued[delayJobNum].sObjectQuery__c;                    try{
                        Id unfollowRulesProcessId = Database.executeBatch(unfollowDelayedRecords, 200); 
                        batchJobsToDelete.add( batchJobsQueued[delayJobNum]);
                    } catch(exception e){
//                        system.debug('Either the batch failed or the job deletion from teh queue failed: '+e);
                    }//try
                } else if(batchJobsQueued[i].delayRulesIncluded__c==FALSE){
                 //is this the simple case with no "days delay" rules?
                    UnfollowRecordsBatch  unfollowRecords= new UnfollowRecordsBatch();
                    unfollowRecords.ObjectName=batchJobsQueued[i].objectName__c;
                    unfollowRecords.numRulesUsedInThisObject=batchJobsQueued[i].numRulesUsedInThisObject__c.intValue();
                    unfollowRecords.sObjectQuery =  batchJobsQueued[i].sObjectQuery__c;
                
                    try{
                        Id unfollowRulesProcessId = Database.executeBatch(unfollowRecords, 200); 
                        batchJobsToDelete.add(batchJobsQueued[i]);
                    } catch(exception e){
//                        system.debug('Either the batch failed or the job deletion from the queue failed: '+e);
                    }//try
                } else {
                //else it's the more complex case where we need to check for the unfollow date
                    UnfollowQueueDelayRecordsBatch queueDelayRecords= new UnfollowQueueDelayRecordsBatch();
                    queueDelayRecords.ObjectName=batchJobsQueued[i].objectName__c;
                    queueDelayRecords.sObjectQuery =  batchJobsQueued[i].sObjectQuery__c;
                    queueDelayRecords.evalateEachRecordForDaysDelay=batchJobsQueued[i].evalateEachRecordForDaysDelay__c;
                    
//let's cross our fingers that the rule criteria didn't change between when this job first ran and now :(  
//Will the code fail elegantly if the rules were changed?
//I'd rather not create a 3rd queue just to save the state of the rules due to stupid batch apex limits
                    queueDelayRecords.delayRules=[Select Id, ObjectName__c, Active__c, FieldName__c, FieldType__c, Operator__c, Value__c, DaysDelay__c FROM UnfollowRule__c WHERE DaysDelay__c>0 AND Active__c = TRUE AND objectName__c=:queueDelayRecords.ObjectName]; 

                    try{
                        Id unfollowRulesProcessId = Database.executeBatch(queueDelayRecords, 200); 
                        batchJobsToDelete.add( batchJobsQueued[i]);
                    } catch(exception e){
//                        system.debug('Either the batch failed or the job deletion from the queue failed: '+e);
                    }//try
                    
                }//if 2
            }//for 1
            try{
            	delete batchJobsToDelete;
            } catch(exception e){
//                        system.debug('job deletion from the queue failed: '+e);            
            }//try delete

        }//if 1


    }//unfollowTryBatchJobsAgain
}//unfollowTryBatchJobsAgain

 

: