0

Problem

I have process that is a Queueable Chain that calls 9 different Queueable Classes (below is 2/9 but all follow this pattern). Of these, 8 are making API callouts to another Salesforce Org. Currently, my test classes are at 74% for the Queueable Chain. I have seen this post on Chained Jobs, but it doesn't cover Chained Jobs that make Callouts. When implementing the option from the stackExchange questino for 100% coverage, I got an error saying Methods defined as TestMethod do not support Web service callouts.

Here is an example of the pattern I am following that I got from this question I asked earlier last week. It has served me well thus far, but need to get pushed over the 1%.

Question: How do we test a class that is using Test.isRunningTest(), specifically in Chained Apex Jobs that are making Callouts?

public class que_1_getParentCampaigns implements Queueable, Database.AllowsCallouts{
        public void execute(QueueableContext con){
            System.debug('GETTING PARENT CAMPAIGNS'); 
            SandboxDataRefresh.getFullBoxParentCampaigns(); // gets the parent campaigns
            System.debug('QUE: ChildCampaigns');
            if (Test.isRunningTest() == false) {
                System.enqueueJob(new que_2_getChildCampaigns());    
            }
        }
    }

public class que_2_getChildCampaigns implements Queueable, Database.AllowsCallouts{
        public void execute(QueueableContext con){ // gets the parent campaigns
            System.debug('GETTING CHILD CAMPAIGNS'); 
            SandboxDataRefresh.getFullBoxChildCampaigns(); 
            System.debug('QUE: TraitSchools');
            if (Test.isRunningTest() == false) {
                System.enqueueJob(new que_3_getTraitSchools()); 
            }
        }
    }  

// Pattern remains the same for 3-9 jobs

Test Class: 74% Coverage:

 @isTest
    static void testGetFullBoxParentCampaigns(){
        System.assertEquals(Test.isRunningTest(), true); 
        StaticResource r = [SELECT Id, Body FROM StaticResource WHERE Name = 'Test_ParentCampaignsResponse'];
        FullBoxCalloutService_Mock mock = new FullBoxCalloutService_Mock(); 
        String query = FullBoxHttpRequests.getQueryFromMetaData('getParentCampaignQuery'); 
        mock.resource = r;
        mock.endpoint = baseURL+query;

        Test.setMock(HttpCalloutMock.class, mock ); 
        Test.startTest();
         
        Id AsyncApexJob = System.enqueueJob(new SandboxRefreshHelper.que_1_getParentCampaigns());  
        
        Test.stopTest();
        Campaign parentCampaign = [SELECT Id, Name FROM Campaign WHERE Name = 'Test']; 
        System.assertNotEquals(null,parentCampaign.Id, 'campaign was null'); 
    }

    @isTest
    static void testGetChildCampaigns(){
        Campaign testCampaign = TestDataFactory.createSingleCampaign('Test', 'Default'); 
        StaticResource r = [SELECT Id, Body FROM StaticResource WHERE Name = 'Test_ChildCampaignResposne'];

        
        System.assertNotEquals(null, testCampaign);  
        
        
        FullBoxCalloutService_Mock mock = new FullBoxCalloutService_Mock(); 
        String query = FullBoxHttpRequests.getQueryFromMetaData('getChildCampaignsQuery'); 
        mock.endpoint = baseURL+query;
        mock.resource = r; 
        Test.setMock(HttpCalloutMock.class, mock ); 
        Test.startTest();
 
        Id AsyncApexJob = System.enqueueJob(new SandboxRefreshHelper.que_2_getChildCampaigns());  
        
        Test.stopTest();
        List<Campaign> childCampaigns = [SELECT Id, Parent.Name FROM Campaign WHERE Parent.Name = 'Test']; 
        System.assertNotEquals(null,childCampaigns, 'campaign was null'); 
    }

Possible Solution:

I'm thinking of trying to Override Test.IsRunningTest() however, I suspect I will run into a Apex Depth error as it will queue the next job. Here is the override code I'm looking at implementing:

@TestIsVisible Boolean isTest = false; 
static{
     isTest = Test.IsRunningTest();
}

1 Answer 1

1

Going down this route is a bad idea. Instead, you want to override the "go to next" part as a dependency injection. Your example might look like this:

 public interface SystemEnqueueJob {
   Id enqueueJob(Queueable job);
 }
 public class ProductionEnqueueJob implements SystemEnqueueJob {
   public Id enqueueJob(Queueable job) {
     return System.enqueueJob(job);
   }
 }
 public class TestEnqueueJob implements SystemEnqueueJob {
   public Id enqueueJob(Queueable job) {
     return null;
   }
 }

And in your main code:

public class que_1_getParentCampaigns implements Queueable, Database.AllowsCallouts{
    @TestVisible SystemEnqueueJob executeNext = new ProductionEnqueueJob();
    public void execute(QueueableContext con){
        System.debug('GETTING PARENT CAMPAIGNS'); 
        SandboxDataRefresh.getFullBoxParentCampaigns(); // gets the parent campaigns
        System.debug('QUE: ChildCampaigns');
        executeNext.enqueueJob(new que_2_getChildCampaigns());    
    }
}

And then override this in your test:

 que_1_getParentCampaigns job = new que_1_getParentCampaigns();
 job.executeNext = new TestEnqueueJob();
 System.enqueueJob(job);

Here, we stop the chaining by overriding the internal implementation.

You'll need one separate unit test for ProductionEnqueueJob just to get that 1 line of coverage, but you'll have total control over chaining your jobs.

The StubProvider interface is an alternative to this as well.

8
  • if I have innerclasses in the Queueable Chain Class, will that fail the Stub Interface? Was watching a Salesforce Dev video that said Inner Classes are a exception? youtube.com/watch?v=SdfDFfdRMdE
    – thinker
    Jun 25, 2021 at 20:01
  • 1
    @thinker Yes, you'd have to break out your classes to use StubProvider. DI (dependency injection) is probably the better way to go here. Still, I think Database.AllowsCallouts might be having problems working on an inner class (I've never tried, but it appears to be the case here); you might need to break out your Queueables into a top-level class, or build a Queueable executor that executes the inner classes, and have that one implement Database.AllowsCallouts.
    – sfdcfox
    Jun 25, 2021 at 20:20
  • 1
    To get an idea of what I'm talking about, take a look at this old answer of mine, where I use a Queueable as a top-level, and pass in jobs for chaining.
    – sfdcfox
    Jun 25, 2021 at 20:21
  • Gonna try and keep the structure of inner classes. It's very readable this way, and hopefully it's not just a test class issue. Will update after I test this out. As always, thank you for saving our butts on this stuff!
    – thinker
    Jun 25, 2021 at 20:36
  • 1
    @thinker They can all be inner classes/interfaces somewhere if you prefer. There's no requirement that they all be separate files. This answer just lists them as such for simplicity.
    – sfdcfox
    Jun 25, 2021 at 21:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .