As end to end test could not be possible with testing trigger and batch separately, another workaund could be is to call the execute's code as future.
Still not perfect but for the current scenario atleast I am able to do an end to end test, following is an example-
Test class:
@IsTest
public class TestAccountTrigger {
@IsTest
private static void testBatchFunctionality() {
// create test data
Test.startTest();
// set mock
// update records
Test.stopTest();
// verify records as processed by batch
}
}
Trigger:
trigger AccountTrigger on Account (after update) {
if(myCondition) {
if(Test.isRunningTest()) {
// call batch's method as future as Database.executeBatch is counted as DML
MyBatchClass.executeCalloutAsFuture(Trigger.newMap.keySet());
} else {
Database.executeBatch(new MyBatchClass(), 1);
}
}
}
Batch apex:
public class MyBatchClass implements Database.Batchable<SObject>,
Database.AllowsCallouts {
public Set<Id> accountSet = new Set<Id>();
public OrderPABXBatch(Set<Id> accountSet) {
this.accountSet = accountSet;
}
// start method
public Database.QueryLocator start(Database.BatchableContext BC){
return getQueryLocator(this.accountSet);
}
private Database.QueryLocator getQueryLocator(Set<Id> accountSet) {
return Database.getQueryLocator([SELECT Id FROM Account WHERE Id IN :accountSet]);
}
// call execute method from test context
@future(callout=true)
public static void executeCalloutAsFuture(Set<Id> orderSet) {
MyBatchClass batchInstance = new MyBatchClass(orderSet);
// Get an iterator
Database.QueryLocatorIterator iterator = batchInstance.getQueryLocator().iterator();
List<Account> accounts = new List<Account>();
// Collect the first record
if(iterator.hasNext()) {
accounts.add((Account) iterator.next());
}
if(!accounts.isEmpty()) {
batchInstance.execute(null, accounts);
}
}
// 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
}
}
}