Working answer, heres the code I wrote to repo your exception:
Batch Class
public class SomeBatchClass implements Database.Batchable<sObject>, Database.AllowsCallouts {
public List<sObject> start(Database.BatchableContext context) {
return [
SELECT Id FROM ContentVersion
];
}
public void execute(Database.BatchableContext context, List<sObject> records) {
HttpRequest accessTokenRequest = new HttpRequest();
System.assertEquals(0, Limits.getDmlRows());
System.assertEquals(0, Limits.getDmlStatements());
new Http().send(accessTokenRequest).getBody();
update records; // make sure we can run dml after callout
}
public void finish(Database.BatchableContext context) {
// do nothing ..
}
}
Batch Test Class
@isTest
public class SomeBatchTest {
@testSetup
private static void Setup() {
ContentVersion documentVersion = new ContentVersion(
Title='Tigers',
PathOnClient='cute_tigers.jpg',
VersionData=Blob.valueOf('tigers pic'),
IsMajorVersion=true
);
insert documentVersion;
}
@isTest
private static void Test() {
// Inserting ContentVersion here also causes Uncommitted Work Pending error..
Test.startTest();
Test.setMock(System.HttpCalloutMock.class, new SomeBatchMock());
Database.executeBatch(new SomeBatchClass(), 1);
Test.stopTest();
}
}
Batch Mock Class
public class SomeBatchMock implements HttpCalloutMock {
public HttpResponse Respond(HttpRequest request) {
HttpResponse response = new HttpResponse();
response.setBody('Dummy Body');
return response;
}
}
It seems like inserting a ContentVersion
anywhere in the test, and then trying to run Database.ExecuteBatch
will cause a failure.
However, it seems like creating your own instance of the batch class and running the individual steps does not cause the same failure!
@isTest
public class SomeBatchTest {
@testSetup
private static void Setup() {
ContentVersion documentVersion = new ContentVersion(
Title='Tigers',
PathOnClient='cute_tigers.jpg',
VersionData=Blob.valueOf('tigers pic'),
IsMajorVersion=true
);
insert documentVersion;
}
@isTest
private static void Test() {
// Inserting ContentVersion here also causes Uncommitted Work Pending error..
Test.startTest();
Test.setMock(System.HttpCalloutMock.class, new SomeBatchMock());
// Database.executeBatch(new SomeBatchClass());
SomeBatchClass someBatch = new SomeBatchClass();
List<ContentVersion> versions = someBatch.start(null);
someBatch.execute(null, versions);
someBatch.finish(null);
Test.stopTest();
}
}
Theres other options here as well, such as saving your contentVersions as JSON, and deserializing them in a test which only calls the execute method, while other tests check each part of the batch class separately. However, it seems like using Database.ExecuteBatch
is out of the question.
Some more gotchas:
- Returning a fixed (created without a query)
List<sObject>
from the start method, if a ContentVersion
is inserted in the test, will fail anyway.
- If the job type is a different type, meaning it won't interact directly with the
ContentVersion
records, it'll still fail if you insert an ContentVersion
record
- If you try to run the same batch job more than once in a context, you'll get the same error, even if the first job was successful
- There are no dml rows, queries, statements, @future methods, or other callouts reported by
Limits
, but the job will fail anyway.
- Trying to call
System.ScheduleBatch
instead will also fail in the same manner as Database.ExecuteBatch
.
- Running the same job by manually by calling the
start
, execute
, and finish
methods will work after inserting a ContentVersion
. However, trying to execute the job twice in the same context will cause a failure (as there are dml rows from that context).
- Both
System.ScheduleBatch
and Database.ExecuteBatch
will return valid job ids before failing.
I'm not 100% sure of the cause, but theres clearly something happening behind the scenes when you insert
a ContentVersion
(probably the creation of relevant objects, such as ContentDocument
's) which is causing the failure of these methods. This feels a lot like a bug, and I'd go as far as to contact support if I were you (I'd do it for you if I had better support options).