6

I'm trying to write test code for a Batchable class that AllowsCallouts. I've written a lot of other test code that uses HttpCalloutMock classes, and planned to do so for this class as well.

And, per best practices, I'm setting up my test data before calling Test.startTest(). Even so, I get a CalloutException:

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

This somewhat makes sense, because my start() method passes a SOQL query to Database.getQueryLocator() and my execute() method makes the web callouts. I'm guessing that when my test method calls test.stopTest(), both start() and execute() run in the same context. In regular batch operation, they happen in separate contexts so there isn't any uncommitted work.

I intend to work around this by calling the methods directly, possibly even calling start() before Test.startTest(), and then using the returned Database.QueryLocator to get the list of records I'll pass into execute().

I don't like the fact that I'm not testing the batch size used by the static method that kicks off the batch with Database.executeBatch(), so I'll probably have that use a constant that I can reference from my test code to ensure it's using the same batch size.

Salesforce kind of addresses this in the "Asynchronous Apex and Mock Callouts" section of the Summer '13 Release Notes, but they don't make reference to Batchable classes with Callouts. They only talk about making callouts and testing Batchable classes in the same test method.

So, my question is whether there is a better way to write this test. I'm also throwing this out so others searching the site can learn about possible workarounds.

  • So there is no DML in your execute before the callouts ? – techtrekker Jun 28 '13 at 18:33
  • Correct. The Batch runs fine outside of the tests. – tomlogic Jun 28 '13 at 18:45
  • You can see my comments on a previous post here: salesforce.stackexchange.com/questions/12820/… But basically, I think you are stuck testing the callout part separately from the batch execution...hopefully, next release will fix this! – BritishBoyinDC Jun 28 '13 at 22:54
4

I know this is a 'hack' but you may be able to call your batchable class methods inside your testmethod without running in a Database.executeBatch(...); method call. I have done this for a customer who needed higher test coverage; their batch apex was simple and didn't require the batchable context to be set.

TestCode:-

    MyBatchableClass mbc = new MyBatchableClass();

        List<MySObject__c> scope = {list of previously inserted objects};
        mbc.execute(null, scope);
        Test.startTest();
        WebServiceMockImpl mock = new WebServiceMockImpl();

        Test.setMock(WebServiceMock.class, mock);
        //Webservice call out in here
        mbc.finish(null);

        Test.stopTest();
1

Richard has the right idea (so I selected his answer as correct), and I just wanted to include my final solution for others to reference.

    // In API 27, we have been unable to test Batchable classes that make
    // callouts, so we emulate the testing instead.  Call the start() method
    // to get the QueryLocator, then iterate to get the records, and then
    // call Test.startTest() and manually submit them to the execute() method.
    // Note that this technique wouldn't work if any of the methods needed to
    // actually reference the Database.BatchableContext object (which we pass
    // as null).

    Database.BatchableContext bc;
    MyBatchableClass mbc = new MyBatchableClass ();
    Database.QueryLocatorIterator iter = mbc.start( bc).iterator();
    List<My_Custom_Object__c> batch = new List<My_Custom_Object__c>();
    Integer batchsize = MyBatchableClass.BATCH_SIZE;
    while (batchsize-- > 0 && iter.hasNext())
    {
        batch.add( (My_Custom_Object__c) iter.next());
    }

    Test.startTest();
    Test.setMock( HttpCalloutMock.class, new MyCalloutMock());
    mhc.execute( bc, batch);
    mhc.finish( bc);
    Test.stopTest();

    // asserts go here

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.