4

I am writing a test class for batch class. but it is throwing error:

System.LimitException: Too many async jobs enqueued for this apex test context Stack Trace External entry point

I am calling batch class from finish method again, but i dont know how to stop calling it again in test class. I have set body in mock class that is returning just one product. Please guide me how can i make it call once.

public class ShopifyFetchProductsBatch implements Database.Batchable<ShopifyAPI.ShopifyProduct>, Database.AllowsCallouts, Database.Stateful 
{
    public integer pageNumber;
    public boolean doMore;
    public ShopifyFetchProductsBatch(integer pNumber)
    {
        pageNumber = pNumber;
        doMore = true;
    }

    public Iterable<ShopifyAPI.ShopifyProduct> start(Database.BatchableContext BC)
    {
        system.debug('Start:' + pageNumber);
        List<ShopifyAPI.ShopifyProduct> prods = ShopifyApi.GetAllProducts(50,pageNumber).products;
        doMore = prods.size() > 0;
        return prods;
    }   

    public void execute (Database.BatchableContext BC, List<ShopifyAPI.ShopifyProduct> prods)
    {
        system.debug('Execute:' + (prods.size()));
        List<Shopify_Product__c> sProds = new List<Shopify_Product__c>();
        List<Shopify_Product_Variant__c> sVars = new List<Shopify_Product_Variant__c>();
        for(ShopifyAPI.ShopifyProduct p : prods)
        {
            Shopify_Product__c sp = new Shopify_Product__c(name = p.id);
            sProds.add(sp);
        }
        insert sProds;
        for(ShopifyAPI.ShopifyProduct p : prods)
        {
            for(ShopifyAPI.ShopifyVariant var : p.variants)
            {
                Shopify_Product_Variant__c v = new Shopify_Product_Variant__c();
                v.sku__c = var.sku;
                v.shopify_product_id__c = var.product_id;
                v.Name = var.id;
                for(Shopify_Product__c pro : sProds)
                {
                    if(pro.Name == v.shopify_product_id__c)
                    {
                        v.shopify_product__c = pro.ID;
                    }
                }
                sVars.add(v);
            }
        }
        insert sVars;
    }

    public void finish (Database.BatchableContext BC)
    {
        if(doMore)    
        {
            system.debug('Do More:' + (pageNumber+1));
            ShopifyFetchProductsBatch b = new ShopifyFetchProductsBatch(pageNumber+1);
            database.executeBatch(b,50);
        }
        else
        {
            system.debug('No Do More:' + (pageNumber));
            ShopifyUpdateProductsBatch b = new ShopifyUpdateProductsBatch();
            database.executeBatch(b,5);
        }
    }

}

TestClass:

@IsTest(seeAllData=true)

Public Class TestShopifyFetchProductsBatch
{   
    Static testMethod void Test()
    {               
        Test.StartTest();      
        Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
        ShopifyFetchProductsBatch b = new ShopifyFetchProductsBatch(1);
        b.start(null);
        Database.executebatch(b);
        Test.StopTest();   
    }  
}

3 Answers 3

1

It is difficult to write good tests for cases where one batchable chains to the next.

The most direct way to work around your immediate problem is to make use of the Test.isRunningTest method to turn off the chaining in the finish method when the code is run from a test:

public void finish (Database.BatchableContext BC)
{
    if (!Test.isRunningTest())
    {
        if(doMore)
        {

This is appropriate if the majority of the logic you are trying to test is in the execute method. All you can do to check the chaining functionality is to review the code with other developers and not change it in the future without further careful review.

1
  • Thanks, i have successfully done exactly this, but before you post this answer. I am going to accept the answer. Thanks for your precious time. Jul 19, 2016 at 9:00
3

A pattern that I use often in my trigger handler pattern that may work well here is a static flag to turn off the functionality causing the issue. Something like:

public class MyBatch implements Database.Batchable<SObject>
{
    @TestVisible static bypassChaining = false;

    // ...

    public void finish(Database.BatchableContext context)
    {
        it (bypassChaining) return; // often called a "guard clause"

        // current chaining logic
    }
}

I prefer not to modify my production code to behave differently inside/outside of a test. Here the behavior is more uniform.

You can still cover your finish logic by simply calling the finish method without executing the full batch. If you stop using the deservedly maligned SeeAllData=true flag, you can set up your data so that you do not chain again. Testing batches with this flag set is not advised. If you cannot remove that flag for some reason, you will need to set the flag mentioned above between calling your finish method and calling the Test.stopTest method.

static testMethod void testChaining()
{
    MyBatch instance = new MyBatch();

    Test.startTest();
        instance.finish(null);
        MyBatch.bypassChaining = true;
    Test.stopTest();

    // assertions
}
1
  • Heh I contemplated a few options before settling on that.
    – Adrian Larson
    Jul 16, 2016 at 17:45
0

A quick win is to increase the batch scope so that the execute method can iterate over all the records in one go rather than firing again and again to cover all the record returned by the start method, which anyways is not allowed in the scope of apex tests.

You must log in to answer this question.

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