In this post we’ll share a testing solution for the Apex Continuations implemented in a static-context (commonly used in Aura and Lightning Web Components).

Note: This approach is not an official Salesforce solution.

Apex Continuation Overview

Apex Continuations are a mechanism provided by the Salesforce platform that allow you to make asynchronous long-running requests to an external Web Service. The service supports callouts to a SOAP or REST Web Service, with a maximum timeout of 120 seconds (versus 10 seconds in a standard synchronous callout).

To give a more visual description of this concept, look at the diagram below:

(1) A user invokes an action that requests some information from a Web Service
(2 – 3) The app server sends the request to the Continuation Server and returns to the client-side, creating then an asynchronous situation / context
(4 – 5) From the Continuation Server, a request is sent to the external Web Service
(6 – 7) The Web Service processes a response that is sent back to the Continuation Server
(8) This response is processed back to the App Server
(9) And finally, sent back to the client-side to be displayed to the user

To get into technical details of implementation, this is possible thanks to the definition of two different functions:

  • The first one is responsible of creating, initializing and firing the Continuation. It can receive parameters from the component. We’ll name it startContinuation() in the different examples of this post.
  • The second one is the callback: once the response has been processed and returned, we might want to work with the received data before displaying it to the user or to handle undesired exceptions from the External Service. We’ll name it continuationCallback() in the examples.

Benefits of Apex Continuations

At this point, you may be asking yourself: what’s the point of using these long-running asynchronous callouts, called Continuations, versus other asynchronous alternatives like Queueable jobs? Well, you could use a Queueable job to perform a callout that will be executed asynchronously in the future, but you would lose the context of the execution. Even if you get the job Id, your component won’t have a callback that is executed once the job is finished. Queueable jobs fit better in situations where the user doesn’t need to wait to see the results of the callout.

Implementing and Testing Apex Continuations

If you have already gone through the Salesforce Developer documentation, you might have seen that the implementation of Apex Continuation is not something new. There are different detailed guides, so let’s not reinvent the wheel! You can find below the official documentation for each context:

The main difference between these three points is that the Visualforce implementation uses non-static methods (it relies on a class instance), whereas Aura and LWC use static methods (this is required for the @AuraEnabled annotation). Apex Continuations were initially developed to be used within class instances (Visualforce controllers). This allowed to maintain individual references in an asynchronous context.

When it comes to testing, the test class implements a method invokeContinuationMethod(Object controller, Continuation request) that allows you to synchronously test your Apex Continuation by invoking the callback method for the specified controller. The first parameter of this function expects an instance of the controller class that implements the continuation and the second one is the continuation object that is returned by an action method in the controller class.

When calling an Apex function from a Lightning web component or an Aura Component, the Apex method must be static and annotated with @AuraEnabled. When combined with the previous points… we can see that there is a problem: the invokeContinuationMethod(controller, request) function is expecting an instance of the controller but our class will be static with static methods.

⛔ PROBLEM: It is not possible to test the Apex Continuation implemented in a static context.

✅ SOLUTION: You can implement @TestVisible variables to store relevant information for the test methods, and replace the invokeContinuationMethod directly by leveraging your callback function.

Note: Another reason why it is is not possible to test the Apex Continuation in a static context is that while the code is in a testing context, all of the normally asynchronous functionality is executed synchronously (we don’t want to wait an undefined time to finish running our tests) and we don’t store a reference to the Continuation that was fired.

There are multiple ways to work with Apex Continuations: from one to multiple callouts within the same Continuation, to different implementations of the callback function. We will cover a few examples, such as:

  • A simple Continuation with one HttpCallout and a callback(Object state) function.
  • A simple Continuation with one HttpCallout and a callback(List<String> labels, Object state) function.
  • Multiple Continuations with two HttpCallouts and a callback(List<String> labels, Object state) function.

Simple Apex continuation

In this example, we’ll show you how you can work with one HTTP callout and one parameter in the callback function, Object state. There will be no data passed from the function that fires the the continuation back to the callback function.

A highlight of this code sample is the use of a continuationState property annotated with @TestVisible. This lets us store the request state for test purposes.

ApexContinuation.cls

ApexContinuation_Test.cls

Simple Apex continuation, passing data to the callback function

In this other example, we’ll show you how you can work with one HTTP callout but this time we will have two parameters in the callback function, List<String> labels and Object state. This time, there will be data being passed from the function initiating the Continuation to the callback function.

ApexSimpleContinuationPassingData.cls

ApexSimpleContinuationPassingData_Test.cls

Multiple Apex continuation

In this other example, we’ll show you how you can work with two or more HTTP callouts with two parameters in the callback function, List<String> labels and Object state. There may or may not be data being passed from the function initiating the Continuation to the callback function.

ApexMultipleContinuation.cls

ApexMultipleContinuation_Test.cls

Running the examples above in a Lightning web component

If you want to use the same principles in a Lightning web component, you can implement it with the following code. In this example the Continuation is called imperatively when the component renders but you can call it any time.

apexContinuation.js

apexContinuation.html

Recommendations

Finally, here are some tips and advice from what I have learned by implementing Apex Continuations:

  1. Always use the callback function with two parameters (List<String> labels, Object state). This will help you have a standard development style in your code (and it is easier to work with test variables).
  2. When implementing Apex Continuations with multiple callouts, create multiple @TestVisible variables (continuationLabelX) instead of a list in the Controller class. This will help you to easily identify which label is referencing which request in the test context.

You can find the source code of the examples in this repository. Don’t hesitate to contribute or leave suggestions there.

About the author

Víctor García Zarco is a Technical Consultant at Salesforce France. He has an IT Engineering and Business background, helps customers to implement Salesforce adapted to their needs and is an innovation and technology enthusiast. Check his GitHub projects @victorgz

References

Examples of the implementation and testing of Apex Continuations in LWC and Aura repository
Apex Developer Guide: Continuation Class
Apex Developer Guide: Make Long-Running Callouts from a Visualforce Page
Apex Developer Guide: Execution Governors and Limits
Lightning Aura Components Developer Guide: @AuraEnabled Annotations for Continuations
Lightning Web Components Developer Guide: Continuations

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

Add to Slack Subscribe to RSS