1

I am trying to write a test for my trigger that will queue up some job, unfortunately according to Salesforce, tests can't have chain-able queues.

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_queueing_jobs.htm

You can’t chain queueable jobs in an Apex test. Doing so results in an error. To avoid getting an error, you can check if Apex is running in test context by calling Test.isRunningTest() before chaining jobs.

So when I run my test I get this error:

System.AsyncException: Maximum stack depth has been reached.

I need to find some way to get around this.

One way I was thinking is to let the trigger know that it's a test, so that it won't queue up the job. Here is my current code:

My Trigger class:

trigger MyTrigger on MyObj(after insert) {
    String CRON_EXP = '0 0 0 ? * * *';
    MyObjScheduler schedule =  new MyObjScheduler ();
    String jobId = System.schedule('MyTrigger', CRON_EXP, schedule);
}

My Scheduler Class:

global class MyObjScheduler implements Schedulable {
    global void execute(SchedulableContext ctx) {
        // Other business logic
        MyObjQueueableJob curJob = new MyObjQueueableJob ();
        System.enqueueJob(curJob); 
    }
}

My Queueable

public class MyObjQueueableJob implements Queueable {
    public void execute(QueueableContext context) {
        // Other business logic
        System.enqueueJob(otherJobs); 
    } }

If there is a way to communicate with Trigger, then I can just write it like this:

trigger MyTrigger on MyObj(after insert) {
    String CRON_EXP = '0 0 0 ? * * *';
    Boolean IsTest = false; // How to change this to true?
    MyObjScheduler schedule =  new MyObjScheduler ();
    if (!IsTest) {
        String jobId = System.schedule('MyTrigger', CRON_EXP, schedule);
    }
}

Is this possible?

1
  • best practice is to put if statements in front of each attempt to start async -- e.g. if(Util.isQueueable()) {System.enqueueJob(..);} , if (Util.isSchedulable()) {System.schedule(..);} so the code runs correctly in either test or prod context. You can use the Limits class methods in the Util.isXXX methods
    – cropredy
    Commented Jul 21, 2020 at 21:15

2 Answers 2

3

If I understood your question correctly, in your trigger, you can write something like this:

String jobId;    
if(!Test.isRunningTest()){
    jobId  = System.schedule('MyTrigger', CRON_EXP, schedule);
}
3
  • oh wow, I didn't know you can do that, let me try that out. Commented Jul 21, 2020 at 19:47
  • 2
    yes but ... your solution makes it impossible to unit test (verify job is scheduled) the trigger
    – cropredy
    Commented Jul 21, 2020 at 21:10
  • @cropredy : Totally agree. The answer should have been more comprehensive. But the OP specifically asked how he could bypass the specific piece of code from being executed when running test. Thats why added this answer. Commented Jul 22, 2020 at 2:30
3

Using if(!Test.isRunningTest()) allows you to purposefully execute (or not execute) code if you're running in a test context.

That can help you out of some simple situations, but a consequence of not running particular bits of code is that the code that does not get run during a test also does not get code coverage from that test.

You're going to need to cause your code to run at some point, and chained queueable jobs are likely still going to present an issue.

Option one

One potential answer to that is to stick to only writing unit tests (as opposed to, say, integration tests). The test only stresses one class, and any method from any other class that gets called needs to be stubbed/mocked.

This approach requires extra setup if you're using the Apex Mocks framework or The Stub API.

It may also mean that you need to design and architect your code in such a way that you can perform dependency injection.

Option two

Another option is to use Test.isRunningTest() to determine when you should enqueue your queueable job, and when you should just call the execute() method like it were any other method.

It may not be obvious at first, but there really is nothing stopping you from doing the following:

MyObjQueueableJob asyncWork = new MyObjQueueableJob();
asyncWork.execute(null);

The QueueableContext parameter can just have null passed with it. QueueableContext is an interface that provides one method, getJobId(), and that information doesn't really have much in the way of uses.

The advantage of this option is that you still run your queueable code in your test, and you can work around the chaining restriction.

The drawback of this approach is that everything you're running (in the test) is no longer async, and you won't get a fresh set of limits like you would outside of a test context. Running into governor limits may be a very real concern here.

One last note

Using Test.isRunningTest() to alter the path of execution in test contexts likely means that you won't be able to attain 100% coverage.

If you ask me though, 100% coverage is a nice thing to strive for but not really all that important. It's far more important that you write many test methods to cover a wide range of possible code paths (which naturally leads to high coverage) and make a ton of assertions (to validate that running your code ends up doing what you think it should be doing).

You must log in to answer this question.

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