Testing Asynchronous Methods in the Cloud

One of the relatively recent additions to the Force.com platform was the ability to make calls to asynchronous methods from Apex classes.  By using the @future (callout=true) annotation, we can set up the method to be called in a seperate, asynchronous request.  This gave the developers the ability to run logic outside the current transaction, create asynchronous data loads, etc.  But it also presented one obstacle… The method could only be called asynchronously.  Which made testing a problem.

Take the following code snippet for example.  It simply deletes Lead objects in the database (not a very practical example, but hey - it is just an example!).
01       @future (callout=true)
02       public static void deleteLeads(List leadIds) {
03              String queryString = ‘SELECT Name from Lead where ‘;
04              Integer numIds = 1;
05              for (String id: leadIds) {
06                     if (numIds > 1) {
07                            queryString = queryString + ‘ OR ‘;
08                     }
09                     numIds = numIds + 1;
10                     queryString = queryString + ‘ID=’’ + id + ‘’’;
11              }
12              
13              List leadsToDelete = Database.query(queryString);              
14              delete leadsToDelete;
15       }

At first glance, testing looks like it would be easy enough.  Simply identify the objects you want to perform the logic on, call the method, and then test to see if the logic was run on the objects.  Right?
01       static testMethod void testDeleteLeads() {
02              
03              String uniqueLastName = ‘TEST’ + System.now();
04              Lead duplead = new Lead();
05              duplead.company= ‘test’;
06              duplead.lastname = uniqueLastName ;
07              insert duplead;       
08              
09              Lead duplead2 = new Lead();
10              duplead2.company= ‘test2’;
11              duplead2.lastname = uniqueLastName ;              
12              insert duplead2;
13              
14              List duplist = [SELECT name from Lead where lastname=:uniqueLastName ];
15              
16              List duplicateLeadIds = new List();
17              duplicateLeadIds.add(’’ + duplist.get(0).id);
18              duplicateLeadIds.add(’’ + duplist.get(1).id);
19              
20              deleteLeads(duplicateLeadIds);
21
22              List duplist2 = [SELECT name from Lead where lastname=’test’];
23              System.assert(duplist2.size()==0);
24       }

Unfortunately, this test fails every time.  Why?  because the deleteLeads method is not called in the same request - meaning that when the logic to delete the Leads has not been run when the assert takes place.  So how do we test this?  The solution is kind of hacky, but it works well.  Simply move all of the logic out of the asynchronous method and into a normal method that the asynchronous method calls.

01       @future (callout=true)
02       public static void deleteLeads(List leadIds) {
03              deleteLeads_nonAsynch(leadIds);
04       }
05
06       public static void deleteLeads_nonAsynch(List leadIds) {
07              String queryString = ‘SELECT Name from Lead where ‘;
08              Integer numIds = 1;
09              for (String id: leadIds) {
10                     if (numIds > 1) {
              ...

While the deleteLeads method is still untestable, the deleteLeads_nonAsynch can easily be tested by changing line 20 in the test method to read:
20              deleteLeads_nonAsynch(duplicateLeadIds);

We now have a high degree of test coverage, and are accurately testing the function of the code itself.  Is it pretty?  Nope.  But it works.

Comments

Be the first to comment!

Leave A Comment

Please help us stop spam by typing the word you see in the image below: