[SalesForce] Unit Test: System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out

BACKGROUND

I am testing a method that results in two external calls out to an API.

I have this Apex unit test:

@isTest
public static void createInvoice_Success(){

    // arrange      
    StaticResourceCalloutMock contactMock = getStaticResourceCalloutMock(200, 'ContactsMock');
    StaticResourceCalloutMock invoiceMock = getStaticResourceCalloutMock(200, 'InvoicesMock');

    Map<String, HttpCalloutMock> mocksMap = new Map<String, HttpCalloutMock>();

    mocksMap.put('https://api.example.com/api/2.0/Contacts', contactMock);
    mocksMap.put('https://api.example.com/api/2.0/Invoices', invoiceMock);

    HttpCalloutMock mocks = new MultiRequestMock(mocksMap);

    Test.setMock(HttpCalloutMock.class, mocks);

    Appointment__c appointment = TestDataFactory.createAppointment();
    Id invoiceId = TestDataFactory.createInvoice(appointment);
    Id invoiceLineId = TestDataFactory.createInvoiceLine(invoiceId, appointment.Id);

    CustomAction.Request request = new CustomAction.Request();
    request.invoiceId = invoiceId;

    // act
    Test.startTest();
    List<CustomAction.Response> responseList = CustomAction.execute(new List<CustomAction.Request> {request});
    Test.stopTest();

    // assert
    System.assertEquals(true, responseList[0].isSuccess);
}

There is also a @testSetup method:

@testSetup
    static void testSetup () {

        insert new Custom_Settings__c(
            SetupOwnerId = Userinfo.getOrganizationId(),
            Key__c = 'XXXXXXXXXXXXXXXXXXXXXXXXX',
            Endpoint__c = 'https://api.example.com/api/2.0/'
        );

    }

Additionally, the test method uses this helper method:

private static StaticResourceCalloutMock getStaticResourceCalloutMock(Integer statusCode, String staticResourceName) {

    StaticResourceCalloutMock mock = new StaticResourceCalloutMock();

    mock.setStaticResource(staticResourceName);
    mock.setStatusCode(statusCode);
    mock.setHeader('Content-Type', 'application/json');

    return mock;
}

The class CustomAction has an Invocable Method such that it may be used by Process Builder

QUESTION

Why am I getting this error message:

System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out

What uncommitted work is it referring to?

What is the solution to making this unit test work?

Best Answer

The issue related to the order of actions:

  1. Make a call out to the API
  2. Save the result of API call
  3. Make another call out to the API
  4. Save result of the second API call

I fixed the unit test by only doing one API call out per Invocable Method