Future methods must be static methods, and can only return a void type. The specified parameters must be primitive data types, arrays of primitive data types, or collections of primitive data types. Notably, future methods can’t take standard or custom objects as arguments. A common pattern is to pass the method a List of record IDs that you want to process asynchronously.
public class SomeClass {
@future
public static void someFutureMethod(List<Id> recordIds) {
List<Account> accounts = [Select Id, Name from Account
Where Id IN :recordIds];
// process account records to do awesome stuff
}
}
The reason why objects can’t be passed as arguments to future methods is because the object can change between the time you call the method and the time that it actually executes. Remember, future methods are executed when system resources become available. In this case, the future method may have an old object value when it actually executes, which can cause all sorts of bad things to happen.
To make a Web service callout to an external service or API, you create an Apex class with a future method that is marked with (callout=true)
. The class below has methods for making the callout both synchronously and asynchronously where callouts are not permitted. We insert a record into a custom log object to track the status of the callout simply because logging is always fun to do!
public class SMSUtils {
// Call async from triggers, etc, where callouts are not permitted.
@future(callout=true)
public static void sendSMSAsync(String fromNbr,
String toNbr,
String m) {
String results = sendSMS(fromNbr, toNbr, m);
System.debug(results);
}
// Call from controllers, etc, for immediate processing
public static String sendSMS(String fromNbr, String toNbr, String m) {
// Calling 'send' will result in a callout
String results = SmsMessage.send(fromNbr, toNbr, m);
insert new SMS_Log__c(to__c=toNbr,
from__c=fromNbr,
msg__c=results);
return results;
}
}
Testing future methods is a little different than typical Apex testing. To test future methods, enclose your test code between the startTest
and stopTest
test methods. The system collects all asynchronous calls made after the startTest
. When stopTest
is executed, all these collected asynchronous processes are then run synchronously. You can then assert that the asynchronous call operated properly.
Here’s our mock callout class used for testing. The Apex testing framework utilizes this ‘mock’ response instead of making the actual callout to the REST API endpoint.
@isTest
global class SMSCalloutMock implements HttpCalloutMock {
global HttpResponse respond(HttpRequest req) {
// Create a fake response
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setBody('{"status":"success"}');
res.setStatusCode(200);
return res;
}
}
The test class contains a single test method, which tests both the asynchronous and synchronous methods as the former calls the latter.
@IsTest
private class Test_SMSUtils {
@IsTest
private static void testSendSms() {
Test.setMock(HttpCalloutMock.class, new SMSCalloutMock());
Test.startTest();
SMSUtils.sendSMSAsync('111', '222', 'Greetings!');
Test.stopTest();
// runs callout and check results
List<SMS_Log__c> logs = [select msg__c from SMS_Log__c];
System.assertEquals(1, logs.size());
System.assertEquals('success', logs[0].msg__c);
}
}
Since every future method invocation adds one request to the asynchronous queue, avoid design patterns that add large numbers of future requests over a short period of time. If your design has the potential to add 2000 or more requests at a time, requests could get delayed due to flow control. Here are some best practices you want to keep in mind: