2

I have a Schedulable class. My execute method has a future callout in it, and running it by using System.schedule doesn't run that callout:

Test.startTest();
MySchedulableClass msc = new MySchedulableClass();
String cronExp = '0 0 0 1 1 ? 2050';
System.schedule('test',cronExp,msc);
Test.stopTest();

I can just test it by just running the execute method:

Test.startTest();
MySchedulableClass msc = new MySchedulableClass();
SchedulableContext sc = null;
msc.execute(sc);
Test.stopTest();

But I saw in some comments that that is very bad practice for some reason. What's wrong with it?

  • 2
    My 2c... We are all paid to get a decent job done in a reasonable amount of time. Unfortunately the term "best practice" implies "only practice" or "if you don't do this you are doing it wrong". See No Best Practices for a bit more depth on the dangers of this term. I suggest if you get a good answer here on how to avoid the direct execute call for this case then great. Otherwise leave it in there and move on to the next thing that matters to your customer. – Keith C Oct 17 '18 at 21:08
1

The main reason you should not call execute directly is because it does not reset your governor limits, which may give you bad test results. For example, you can't call as many Queueable classes in asynchronous code as you can in synchronous code, so your test will pass, but fail in production. Alternatively, your code might exceed the synchronous heap limit, CPU limit, etc, artifically failing during deployment even though your code should work in practice.

There are times when it is okay to use the latter, but it should be avoided whenever possible. Please note that "best practices" doesn't mean "this is a law that must not be violated," but rather "this is a good thumb of rule you should observe as much as practical." In many cases, it doesn't matter, and in some cases, you very explicitly need to call the execute method directly because it violates a unit test limit that doesn't exist in production mode.

For example, if you have a chaining Batchable class, you might need to call the execute methods directly so you can test them without triggering the chaining batch jobs, which would violate unit test rules, but would work okay in normal execution. So, as you can see, there are times where calling execute directly must be done, but it should be considered a last resort in order to make sure you're getting accurate test results.

2

One way around this is to adopt a more rigorous unit test strategy ...

  • Create a class Callout with a method doCallout(some arg list)
  • Have your schedulable execute() do a new Callout().doCallout(args);
  • Use ApexMocks or equivalent to mock the Callout class.
  • Use ApexMocks or equivalent to verify that the method doCallout(args) was called with the args you expect. The mocked object does nothing (or returns a mock result to execute() -- application-dependent)
  • Write separate unit tests for Callout.doCallout(args)

That is, you unit test the schedulable to ensure the callout object is called with expected args; and, you unit test the callout object with args to verify it generates the expected callout and you can handle the mocked HttpResponse.

Now, as sfdcfox would say/said, this is not an integration test and you aren't testing limits . As you have realized, Test.stopTest() won't execute every cascading async transaction that occurs after executing System.schedule so compromises are required.

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.