10

Rather than creating a separate Schedulable class which executes my Batchable class (which in turn has a callout), shouldn't I be able to have my Batchable class also implement the Schedulable interface and in the execute(SchedulableContext) method, new up the same Batchable class and pass it to the Database.executeBatch method?

This works fine when I execute the schedule from the dev console like so:

Batch_APNEmail batchApex = new Batch_APNEmail();
batchApex.execute(null);

But when I create a scheduled job through the Salesforce web interface and configure it to execute daily at 9am, it throws the exception:

System.CalloutException: Callout from scheduled Apex not supported.

My schedulable/batchable class declaration looks like this:

global class Batch_APNEmail implements Database.Batchable<sObject>, Database.Stateful, Database.AllowsCallouts, Schedulable

My Scheduleable execute method within the schedulable/batchable class looks like this:

global void execute(SchedulableContext sc){
    Batch_APNEmail batchAPN = new Batch_APNEmail();
    Database.executeBatch(batchAPN, 1);
}

Here's a unit test which runs successfully:

@isTest static void test_Schedule(){
    Create_APNs();

    // Setup the mock callouts with static resources
    Setup_Static_Resources();

    Test.startTest();
        Batch_APNEmail batchApex = new Batch_APNEmail();
        batchApex.execute(null);
    Test.stopTest();

    // Verify the callouts were made
    List<Academic_Progress_Notification__c> apns = [SELECT Emailed_Student__c FROM Academic_Progress_Notification__c];
    for (Academic_Progress_Notification__c apn : apns){
        System.assertEquals(apn.Emailed_Student__c != null, true);
    }
7

Yes. In general, you can combine as many interfaces as you want within a single class, so long as they are all compatible. Using the scheduable and batchable interface within the same class just means you have to implement all four methods. Your class will start off like:

public class x implements Database.Batchable<Object>, Database.AllowCallouts, Scheduable {

As an aside, you could also use Scheduable to call a future method, which may be simpler if you need just one callout (or, less than the 10 limit). In which case:

@future(callout=true) public static void doCallout() {
  • 1
    From the looks of his question he is doing exactly as you state in your answer yet he is getting issues. Do you see positive results with a callout using your methods? If so then the OP has something else going on and will need to post more code. – Eric Aug 13 '14 at 14:27
  • 1
    No, you have to use Test.startTest and Test.stopTest. See salesforce.com/us/developer/docs/apexcode/Content/… for information on a proper test. – sfdcfox Aug 14 '14 at 0:18
  • 1
    That's an incorrect test. You must schedule the class and let it execute naturally. Do not call execute(null), because then it is being called in the wrong context. – sfdcfox Aug 14 '14 at 14:39
  • 2
    The point of test.stoptest is to execute any asynchronous code that's been called. Future, scheduled, and batch code all fall into this category. Without this context switch, you can't prove that code is working in the intended fashion. For example, the scheduablecontext value being null isn't a real world use case. All scheduled executions have a Scheduable context. – sfdcfox Aug 19 '14 at 15:13
  • 2
    Also, as I've pointed out, certain calls aren't allowed in certain contexts. The only way to catch this problem is to unit test or live test. Since the scheduler is failing in the real world, the unit test isn't matching the behavior. – sfdcfox Aug 19 '14 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.