0

I have a problem with FeedItems in my Test class, it's very weird behaviour Does someone explain to me why this happens?

Callout Class

public with sharing class Callout {

public static void runProccess(Case[] records) {
    Database.executeBatch(new CalloutBatch(records), 10);
}

public static void send(Case[] records) {
    Case[] toUpdate = new Case[]{};


    for (case record : records) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:test/SOME_URL');
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody('');
        Http http = new Http();
        HTTPResponse res = http.send(req);

        Map<String, Object> response = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());

        toUpdate.add(new Case(Id = (Id) response.get('Id'), Status = (String) response.get('Status')));
    }

    update toUpdate;

}
}

CalloutBatch Class

global with sharing class CalloutBatch implements Database.Batchable<Case>, Database.AllowsCallouts{

private final Case[] records;

global CalloutBatch (Case[] records) {
    this.records = records;
}

global Iterable<Case> start(Database.BatchableContext BC) {
    return records;
}

global void execute(Database.BatchableContext BC, Case[] scope) {
    Callout.send(scope);
}

global void finish(Database.BatchableContext BC) {
}
}

Test Class

@IsTest

private class CalloutTest {
@IsTest
static void shouldReaciveData() {
    String status = 'Closed';
    Case caseRecord = new Case(Type = 'Web', Status = 'New');
    insert caseRecord;

    //insert new FeedItem(ParentId = caseRecord.Id, Visibility='AllUsers', Type = 'TextPost', Body = '');

    System.assertEquals(0, [SELECT Count() FROM Case WHERE Id = :caseRecord.Id AND Status = :status]);

    Test.startTest();
    Test.setMock(HttpCalloutMock.class, new CalloutTestMock(caseRecord.Id, status));
    Callout.runProccess(new Case[]{caseRecord});
    Test.stopTest();

    System.assertEquals(1, [SELECT Count() FROM Case WHERE Id = :caseRecord.Id AND Status = :status]);
}
}

Mock Class

public with sharing class CalloutTestMock implements HttpCalloutMock {

private Id recordId;
private String status;

public CalloutTestMock(Id recordId, String status) {
    this.recordId = recordId;
    this.status = status;
}

public HTTPResponse respond(HTTPRequest req) {
    HttpResponse response = new HttpResponse();

    response.setStatusCode(200);
    response.setBody('{"Id": "' + recordId + '", "Status": "' + status + '"}');

    return response;
}
}

This code works, but when I uncomment insert FeedItem in Test Class I receive an error message:

System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out Class.Callout.send: line 23, column 1 Class.CalloutBatch.execute: line 20, column 1

FeedItem doesn't have an impact on this functionality, (I don't have any Process Builders, triggers etc. on FeedItem object)

0

1 Answer 1

7

Calling Database.executeBatch method from runProccess actually inserts a record of ApexAsyncJob in the database. And as you have written that code in between startTest and stop test it will try running your batch and do your callout. And thus you will get your result uncommited work pending error.

The solution is to directly call the batch execute in between test.StartTest and Test.stopTest

Test.startTest();
Test.setMock(HttpCalloutMock.class, new CalloutTestMock(caseRecord.Id, status));
new CalloutBatch(new Case[]{caseRecord}).execute(null,new Case[]{caseRecord});
Test.stopTest();
3
  • thx, your solution works, but could you explain to me why when insert FeedItem is commented my code works (I calling Database.executeBatch method from runProccess), but when it is uncommented I am receiving an error message? Commented Dec 10, 2018 at 14:49
  • It looks like insert FeedItem is an async operation that inserts in the database after Test,stopTest so does your batch. Thus you face the error when u use feed item. Commented Dec 10, 2018 at 14:57
  • I guess it's also worth mentioning that if you have important logic in start() and/or finish() to test, you can run them all separately, by manually querying for what would be the expected scope and then running batch.start(null); batch.execute(null, scope); batch.finish(null); Commented Jan 16, 2020 at 11:55

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