Testing HttpCallout with HttpCalloutMock and UnitTest Created Data
One option, although not ideal, would be to use @isTest(SeeAllData=true)
rather than creating the Case in your test case.
You can't make callouts once you have made changes to the database (in this case the Case insertion proceeds the callout). It seems like this applies to test case setup as well even though it occurs prior to the Test.startTest()
.
Another option would be to make the mock callout in an @future
method and invoke this future method within the Test.startTest()
and Test.stopTest()
. Updated - This doesn't resolve the issue (at least if the future method is defined in a test class). The CalloutException still occurs with the future method appearing at the bottom of the stacktrace.
To save others the effort, the test data mixed mode DML solution of wrapping the setup code in System.runAs(user) { ... }
doesn't help either. Thought it was worth a long shot, but it didn't help.
For reference, the forum post Test method custom setting and callout - Uncommitted work pending covers much the same question. They ultimately reference another post by Bob Buzzard with the suggested resolutions:
You can't make callouts, HTTP or otherwise, once you have made changes to the database. You either need to commit the transaction, make the callout prior to any database changes or move your callout to an @future method.
Ideas: Allow loading test data prior to callout testing is worth promoting.
The workaround I used is to manually keep track of the mock response, and call the respond method on that instead of Http.send when running a test.
In your callout class it would look something like:
public class CalloutClass {
public static HttpCalloutMock mock = null;
public static HttpResponse getInfoFromExternalService(Id myCaseId) {
Case myCase = [SELECT Id, Account.Integration_Key__c FROM Case WHERE Id = :myCaseId LIMIT 1];
HttpRequest req = new HttpRequest();
req.setEndpoint('http://api.salesforce.com/foo/bar?id=' + myCase.Account.Integration_Key__c);
req.setMethod('GET');
if (Test.isRunningTest() && (mock!=null)) {
return mock.respond(req);
} else {
Http h = new Http();
return h.send(req);
}
}
}
and in the test you would replace the Test.setMock by
CalloutClass.mock = new ExampleCalloutMock();
It's somewhat intrusive, and would hide cases where there is real DML being done before a callout. But it least gets the test to work pending a better solution.
This is especially a problem if you have a trigger that calls a future method which makes the callout. The insert needed to fire the trigger will cause the unit test to fail.