40

I have a Controller (FooController) which calls an @future method (bar()) belonging to another class (FooServices).

Ideally in my test class for this Controller (FooControllerTest) I only want to test that the @future method has been called, I don't want to test its effects as that's FooServicesTest's job.

I know that I can use Test.startTest() and Test.stopTest() to see the effects of an @future method in a test class, but is there any way of identifying that a @future method has been called/queued up in a test class?

Since code is generally easier to understand than words, here is an example of what I would like to be able to do:

FooController

public class FooController
{
    public PageReference save()
    {
        // Do some operations...

        // Call my @future method
        FooServices.bar();

        return null;
    }
}

FooServices

public class FooServices
{
    @future
    public static void bar()
    {
        // Do some things in the future...
    }
}

FooControllerTest

@isTest
private class FooControllerTest
{
    @isTest
    private static void save_ValidData_CalloutQueued()
    {
        // Setup my test data...
        FooController controller = new FooController();
        controller.save();
        // Is this part possible? THE BELOW METHOD NAME IS ENTIRELY FICTIONAL
        System.assert(System.isFutureJobQueued('FooServices.bar'));
    }
    // OR...
    @isTest
    private static void save_ValidData_CalloutExecuted()
    {
        FooController controller = new FooController();
        Test.startTest();
        controller.save();
        Test.stopTest();
        // Yet another fictional method...
        System.assert(System.hasFutureJobExecuted('FooServices.bar'));
    }
}

3 Answers 3

26

Yes!

I love this question, as it's one of my pet peeves. People don't test @future calling methods because they don't know it's possible.

Enter the magic of two key methods:

Test.StartTest();
Test.StopTest();

When you utilize these methods -- and you should! they do a couple of key things for you.

  1. they reset your governor limits so you can test if your actual code is going to hit Limits.
  2. Test.StopTest(), actually forces your @future methods to run right now and return before progressing to the next step.

so, in your test example, if you take your exact test and add those two methods thusly:

@isTest
private class FooControllerTest
{
    @isTest
    private static void save_ValidData_CalloutQueued()
    {
        // Setup my test data...
        FooController controller = new FooController();
        Test.StartTest();
        controller.save();
        Test.StopTest();
        // You can actually now assert that yes, it indeed was queued, 
        // and has run, and has returned data. Since this is a callout? 
        // look at the callout mocks for more info on returning mock 
        // data to assert on.
        System.assert(System.isFutureJobQueued('FooServices.bar'));
    }

}

With Regards to just identifying of a job has been queued:

Select ApexClassId, CompletedDate, CreatedById, CreatedDate, ExtendedStatus, Id,
JobItemsProcessed, JobType, LastProcessed, LastProcessedOffset, MethodName, 
NumberOfErrors, ParentJobId, Status, TotalJobItems from AsyncApexJob

This object, (and query above) should list all pending async apex jobs including enqueued @future methods. I expect (but have never tried) that you could build a where clause based on MethodName. ApexClassId would likely work as well, but would be ugly as the Id's may be hard to come by dynamically.

5
  • Thanks for this, but as I started in the question I know that I can use startTest and stopTest to ensure the @future runs and then check the results. What I want is a way of checking if it has been run/queued without checking for any specific results (as the results of the call will be tested by another class specific to that). Commented Jun 16, 2014 at 18:11
  • @AlexTennant - My bad. I've added a bit at the end with a soql query that should help you get near what you want.
    – Kevin P
    Commented Jun 16, 2014 at 20:42
  • 1
    As noted in the comments on @user320's answer, Future jobs are not added to the ApexAsyncJob table in test contexts. I've added an Idea on the Idea Exchange for now: success.salesforce.com/ideaView?id=08730000000l6UYAAY Commented Jun 17, 2014 at 11:02
  • The Salesforce trial head references this particular link and mentioned this : You can query AsyncApexJob to find your future job, but there’s a caveat. "Since kicking off a future job does not return an ID, you have to filter on some other field such as MethodName, or JobType, to find your job. There are a few sample SOQL queries in this Stack Exchange post that might help." So querying few other fields than ID should give us future jobs in the queue Trialhead Link : trailhead.salesforce.com/modules/asynchronous_apex/units/…
    – peevesy
    Commented May 26, 2018 at 19:58
  • 1
    N.B. V43 - there is no method System.isFutureJobQueued()
    – cropredy
    Commented Jul 18, 2018 at 21:27
18

The method looks like this:

static public Boolean isFutureRunning(System.Type type, String methodName) {
    String namespacePrefix;
    String name;

    if (type.getName().contains('.')) {
        //managed (namespaceprefix.classname)
        namespacePrefix = type.getName().substringBefore('.');
        name = type.getName().substringAfter('.');
    } else {
        //unmanaged (classname)
        namespacePrefix = '';
        name = type.getName();
    }

    //find existing futures
    Integer existingFuturesCount = [
        SELECT COUNT()
        FROM AsyncApexJob
        WHERE ApexClass.NamespacePrefix = :namespacePrefix AND ApexClass.Name = :name
        AND MethodName = :methodName
        AND JobType = 'Future'
        AND Status IN ('Queued', 'Processing', 'Preparing')
    ];

    return existingFuturesCount != 0;
}
5
  • I'll give this a go when I'm back in the office, I'm assuming if I the Status clause I can pull out completed jobs? Is the AsyncApexJob table cleaned out between test methods or is it a 'system' style table where tests and your Org share the same data (like the RecordType table)? Commented Jun 16, 2014 at 18:13
  • 2
    @AlexTennant Yes, if you use startTest and stopTest the status should reach Complete. As for visibility, both real and test apex jobs will be seen. But your test jobs don't exist beyond each test method. I'm really lazy and just count them before and after, then look for n + 1 when asserting. Commented Jun 16, 2014 at 19:48
  • 1
    @AlexTennant I've just confirmed that in Test context, Future jobs don't appear in the table. Only Schedulable and Batchable ones. At least it works in real code... heh Commented Jun 17, 2014 at 10:19
  • Oh well... back to the drawing board. It's a useful piece of code to keep around for non-test situations though. Commented Jun 17, 2014 at 10:21
  • For now I've put an Idea on the Idea Exchange: success.salesforce.com/ideaView?id=08730000000l6UYAAY Commented Jun 17, 2014 at 10:59
12

With the newly available Queueable interface this problem becomes more approachable.

  1. replace your @Future annotated method with a Queueable inner class

  2. lean on Limits.getQueueableJobs() to count the jobs before and after

Example

@IsTest public class QueueableTest {

    public class MyJob implements Queueable {
        public MyJob(String param1, String param2) {}
        public void execute(QueueableContext context) {}
    }

    static testmethod void countMyJobs() {

        //this passes ok
        System.assertEquals(0, Limits.getQueueableJobs());

        //better than future
        Queueable job = new MyJob('param1', 'param2');
        System.enqueueJob(job);

        //this also passes ok
        System.assertEquals(1, Limits.getQueueableJobs());

    }

}
1
  • (the Queueable would be in your code... it's just inside the test here for brevity) Commented Apr 28, 2015 at 14:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .