Server Bug Fix: Testing a Future Method that Schedules a Class – Test.stopTest() Does NOT Run Both Synchronously

Original Source Link

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?

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.

Tagged : / / / /

Leave a Reply

Your email address will not be published. Required fields are marked *