1

I have a class with an @future callout method, and that future method schedules an instance of another class which also makes a callout. In effect, if the first callout is successful, it schedules another callout later on. It is all triggered by a simple field update on the Self_Serve_Trial_Request__c object.

In writing the tests, I was pleasantly surprised to see that the field update ran not only the initial future call out, but also the subsequent scheduled callout. I could see this clearly in the debug log, and I assumed it ran them both synchronously. Great.

However, I then realized that any code after Test.stopTest() did NOT see the changes created by both callouts, but instead only saw changes created by the first. Essentially, Test.stopTest() did NOT make both callouts synchronously, it only made the first one synchronously. So I can only access the context after the first callout, and there is no way for me to access the context after the second callout.

How then can one write a complete end-to-end test, and access the context that exists after both callouts?

 Test.startTest();

     Test.setMock(HttpCalloutMock.class, new SelfServeTrialMocks.CreateMachineMock());

     Self_Serve_Trial_Request__c SSTR = [Select ID FROM Self_Serve_Trial_Request__c LIMIT 1];                                           
            SSTR.Decision__c = 'Approved';
     update SSTR;

 Test.stopTest();

 //>>>>> the initial future method runs here, so I can query the record to see those changes... 

     Self_Serve_Trial_Request__c SSTR = [SELECT Environment_ID__c
                                         FROM Self_Serve_Trial_Request__c WHERE ID = :SSTRs.ID];
     system.assert(whatever = whatever);

 //>>>>>>BUT NOW my scheduled class runs here, after any other code that I write. I can see it working clearly in the debug logs, but cannot access it. Is there no way to do so?
2
  • You may be getting uncommitted work pending exception as you are performing a DML between the callouts?
    – Raul
    Commented Jun 4, 2020 at 21:37
  • Nope, no exception, I can see both callouts succeed in the logs
    – number41
    Commented Jun 4, 2020 at 22:00

1 Answer 1

5

This is a classic problem with testing multi-layer Asynchronous Apex. The trick is that Test.startTest() and Test.stopTest() only force synchronous execution of the first layer - in this case, the future method - of asynchronous code.

Any asynchronous functionality enqueued by that layer will ultimately execute, and may in fact be visible in your debug logs, but won't be executed synchronously such that your unit test can assert against its results.

How then can one write a complete end-to-end test, and access the context that exists after both callouts?

You really cannot.

To effectively test multi-layer async code, you have to decompose it and test each unit individually, and/or apply dependency injection to evaluate the interaction between the two. Here, you'd write one unit test for the future method, and you can write queries against CronTrigger to validate that it enqueued your scheduled class as expected.

Then, write additional unit tests for the functionality of the scheduled class, and in those unit tests, create the right conditions for it to run by inserting the data directly (don't call your future method) before explicitly enqueuing the schedulable.

1
  • Thanks, it's funny this doesn't seem to be explained in any documentation I've seen. On a related note, I thought about querying CronTrigger, and I discovered something else - when you query the CronTrigger object in a test class, it returns all actual scheduled jobs in your organization! Not just the ones you have created in your test! This is actually worse than the async issue - why on earth would Salesforce make everything else test-specific in test classes, but return actual data for Cron jobs? It makes it almost impossible to know exactly which job(s) you are querying at run time.
    – number41
    Commented Jun 5, 2020 at 5:01

You must log in to answer this question.

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