3

I want to do the following in a unit test:

  1. Create a test user to invoke System.runAs with
  2. Update a case record and then invoke a future method that has a callout in it
  3. Check the result

Turns out I can do 2 & 3 OK but by doing 1, I get a callout exception "You have uncommitted work pending. Please commit or rollback before calling out". Some simplified code below demonstrates the problem. If you comment out the line that inserts the user, the test passes. This is even without me actually using System.runAs(). For now, I have worked around it by querying an existing user with the right characteristics and using that as the test user. Don't really like having that dependency but it's the only way I can see to get around this. Can anyone suggest anything better, or at least point me at some documentation that explains this behaviour?

Have included a simple future method that just does a callout. Have not included code for mock callout but it's pretty standard stuff.

    @future(callout = true)
static void myFutureMethod() {
    Http h = new Http();
    HttpResponse res;
    String body = '{"info": "some content to post"}';

    HttpRequest req = new HttpRequest();
    req.setHeader('Content-Type', 'application/json');
    req.setEndpoint('callout:some/webservice');

    req.setMethod('POST');
    req.setBody(body);
    res = h.send(req);
}

static testmethod void calloutExceptionDueToUserObjectDML() {
    Id profId = [select ID from profile where name= 'Standard User'].Id;
    User u = new User( Username = 'sometestusername@test1254.com', FirstName='testy', LastName = 'testuser',
                        Alias = 'tst', CommunityNickname = 'testuser', TimeZoneSidKey = 'Australia/Sydney', LocaleSidKey = 'en_AU',EmailEncodingKey = 'UTF-8',
                        LanguageLocaleKey = 'en_US', Email = 'testy@email.com', ProfileId = profId);
    insert u; // commenting out this line allows the test to execute successfully

    GenericMockCallout callout = new GenericMockCallout(202, 'Great', '{}');
    Test.setMock(HttpCalloutMock.class, callout);
    try {
        Test.startTest();
        // In my real unit test, I would do some DML here running as the test user and then
        // let a future method with a callout get executed when stopTest is executed

        // This test is just showing that the problem occurs even with no DML statements executed
        myFutureMethod();

        Test.stopTest();
    }
    catch(CalloutException e) {
        System.assert(false, 'Callout exception thrown. Message: ' + e.getMessage());
    }
}
  • You're getting an exception with just the future call between startTest and stopTest? – Adrian Larson Oct 16 '16 at 22:12
  • Exception is thrown when stoptest is invoked, which causes the future method to execute. – donmac23 Oct 16 '16 at 22:39
  • Did you cut out some of the future method? Does it perform DML? – Adrian Larson Oct 16 '16 at 22:40
  • In my real code, the future method is called from some trigger code. Inside the future method, there's a callout and then some dml. In the example I've posted, there is no dml other than inserting the user record. – donmac23 Oct 16 '16 at 22:43
  • 1
    Actually you can't do dml before a callout. That's when you would get the error I'm getting but it is ok to do so if your dml is inside a unit test start/stop pair. The stop method then invokes the future method in a different context, which works fine except in this particular case (where in any case I have cut out the dml). – donmac23 Oct 16 '16 at 22:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.