I am trying to write unit test for a a trigger which calls batch apex which performs callout and updates the response to certain record.

Trigger's psuedo code looks like:

trigger AccountTrigger on Account (after update) {
    if(myCondition) {
        Database.executeBatch(new MyBatchClass(), 1);

And batch is:

public class MyBatchClass implements Database.Batchable<SObject>, 
    Database.AllowsCallouts {

    // only relevant execute method
    public void execute(Database.BatchableContext batchableContext, 
    List<Account> accounts) {

        for(Account account: accounts) {

            // make http request callout

            // based on response update account

When I run my test test, it gives me error as:

System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out

From the related question, I understand that Database.executeBatch is treated as a DML. When implementing this solution, I get:

System.CalloutException: Callout from triggers are currently not supported.

Please suggest any workaround or point towards any gap in the design. Any insight is highly appreciated.

  • I see you are not passing any parameters to batch. Are you using trigger only to invoke batch? Have you tried future in that case? – salesforce-sas Jul 29 at 11:40
  • This is a psuedo code. Extra details were not relevant for the issue, hence I did not post them. Can add if necessary. – Raul Jul 29 at 11:43

Presumably, your test class is using Test.startTest() and Test.stopTest() to force your batch class to run synchronously. You then receive a CalloutException because the callouts that would in normal production use run asynchronously in a separate transaction are forced to run in the context of the current transaction - where you've either

  • already run DML or DML-equivalent operations, such as enqueuing a batch class.
  • or are in a trigger context.

Exactly how this works mechanically isn't very clear, but neither are the exact mechanics of running async Apex synchronously.

You should be able to get around this issue by testing your batch class piecemeal - calling each method start(), execute(), and finish() without actually enqueuing the batch. Provided that you sequester your test setup in an @testSetup method and don't perform any further DML in your test method, you should be able to call execute() directly with mock input and have its callouts successfully directed to your configured HttpCalloutMock.

I think you'll be able to succeed in testing the trigger part of your code by failing to wrap your DML in Test.startTest() and Test.stopTest(). You'll be able to validate by querying AsyncApexJob that your trigger enqueues a batch job, but the job itself won't run within the test context, so you won't be able to do an integration test across the code.

  • So I have to basically use Test.isRunningTest() in trigger to skip batch call, then test trigger and batch separately? – Raul Jul 29 at 13:09
  • @Raul I don't think you need to use Test.isRunningTest() (although I might be wrong) - I think if you just don't use Test.startTest() while executing the trigger you'll be able to get it through. And yeah, test the trigger and batch separately. – David Reed Jul 29 at 13:11
  • Its a shame that this doesn't work, thanks for your answer and better insight. Test.isRunningTest() works but I try to avoid it, unfortunately not in this case. For end to end testing I guess I can call a future method instead of batch from trigger for test context – Raul Jul 29 at 14:44

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.