With the recent Spring '15 release, Apex developers have a new asynchronous tool, queueable Apex classes. At first glance, queueable classes are very similar to @future
annotated methods. A closer look, however, reveals their true power. There are three key differences between @future
and queueable classes:
sObjects
@future
methods or batch methods from an @future
contextImplementing the queueable interface is very similar to implementing the batchable or schedulable interfaces. Simply define your class as implementing the queueable interface and implement the execute()
method. Likewise, your implementation of the execute()
method must accept one argument of the QueueableContext
type. This method does not have to be the only method in the class; in fact, you can use constructors and other methods as you see fit. The general pattern is to write the constructor so that it accepts sObjects
and assigns them to class level instance variables. The execute()
method is then used to implement asynchronous logic. Importantly, inside the execute()
method, you can access the JobId
by invoking the getJobId()
method on the QueueableContext
object that the system injects at runtime. With this Id
, you can write SOQL queries to gather data about the object.
Perhaps the clearest real-world example of using the Queueable interface is doing a call out to a web service where you need to log both successes and failures. While an @future
method is capable of making callouts, the Queueable interface is capable not only of callouts but also monitoring and, as of the Spring '15 release, automatically retrying. To help us log successes and failures, we will create a new sObject
called AuditLog__c
, with the following fields:
Field Type |
Name |
Purpose |
---|---|---|
Boolean |
|
Flag for success or failure |
Long Text |
|
JSON representation of the request |
Long Text |
|
JSON representation of the server response |
Id |
|
ID of the async job |
Long Text |
|
Text of the Stacktrace |
Id |
|
ID of the Account this audit log is relates too |
Queueable jobs can be enqueued by calling System.enqueueJob()
with an instance of our queueable class. In this case, we will instantiate our AuditLogGenerator
queueable class with an Account
object. Executing the System.enequeJob()
method with an instance of our AuditLogGenerator
class will return a jobId
. Once the system picks up the job and calls the execute()
method, we can query for the Audit Log record information with that jobId
field. With both the account object passed in via the constructor and the auditlog__AuditLog__c
record, we can make our callout while logging successes and failures as well as automated retries. Here is what that AuditLogGenerator
class looks like:
Public with sharing class auditLogGenerator AuditLogGenerator implements Queueable, Database.allowsCallouts { private account a {get; set;} public AuditLogGenerator(Account incomingAccount) { this.a = incomingAccount; } public void execute(QueueableContext qc) { Audit_Log__c log = [SELECT Id, Success__c, RequestJson__c, ResponseJson__c, Stacktrace__c, AccountID__c FROM Audit_Log__c WHERE JobId__c = :qc.getJobId()]; try { log.RequestJson__c = Rest.GenerateRequestFromAccount(this.a); HTTPResponse response = Rest.makeRestRequestWithUrlMethodAndBody('https://www.example.com', Rest.GET, log.RequestJson__c); if (response.getStatusCode() == 200) { log.Success__c = true; } log.ResponseJson__c = response.getBody(); } catch (Exception e) { log.Success__c = false; log.Stacktrace__c = e.getStackTraceString(); } if (!log.Success__c) { Id retryJobId = System.EnqueueJob(new auditLogGenerator(this.a)); Audit_Log__c retryAuditLog = new Audit_Log__c(JobId__c = retryJobId, AccountID__c = this.a.Id); } update log; } }
Testing Queueable classes is much like testing the @future
methods. The only requirement is proper use of Test.startTest()
and Test.stopTest()
. The asynchronous code executed between Test.startTest()
and Test.stopTest()
methods is forced to run immediately when Test.stopTest()
is called. This greatly simplifies testing. We need only to call our System.EnqueueJob()
method between Test.startTest()
and Test.stopTest()
and make assertions as if it were synchronous, rather than asynchronous code. Consider the following example:
@isTest private class AuditLogGenerator_Tests { static testmethod void test1() { Account firstAccount = (Account) TestFactory.createSObject(new Account()); insert firstAccount; Audit_Log__c log; HTTPMockCalloutFactory fakeResponse = new HTTPMockCalloutFactory(200, 'OK', '{"results":"Ok"}', new Map<String, String>() ); Test.setMock(HttpCalloutMock.class, fakeResponse); Id jobId; Test.startTest(); jobId = System.EnqueueJob(new auditLogGenerator(firstAccount)); log = new Audit_Log__c(JobId__c = jobId, AccountID__c = firstAccount.Id); Test.stopTest(); Audit_Log__c resultingLog = [SELECT success__c FROM Audit_Log__c WHERE jobId = :jobId]; System.assert(resultingLog.success__c); } }
As you can see in the preceding example, Test.startTest()
and Test.stopTest()
bracket our actual enqueueing code. This is all that's required to force the testing framework to run our asynchronous code synchronously within the execution flow of our test.
18.117.99.152