[SalesForce] “Callout loop not allowed” in unit test for Queueable that implements HttpCalloutMock

I'm attempting to do a unit test for a Queueable that does an Http callout. When I run the test I get an exception: "System.CalloutException: Callout loop not allowed". Previously I was using @future rather than Queueable and the test ran fine.

Anyone have an idea of what's happening here?

OutboundMessageSender

public class OutboundMessageSender implements Queueable, Database.AllowsCallouts {
    private Rest_Callout__c restCallout ;
    private String endPoint ;

    public void OutboundMessageSender( Rest_Callout__c rest_Callout )
    {
        restCallout = rest_Callout ;
    }

    public Rest_Callout__c getRestCallout() {
        return restCallout;
    }

    public void setRestCallout( Rest_Callout__c rest_Callout ) {
        restCallout = rest_Callout;
    }

    public String getEndPoint() {
        return endPoint ;
    }
    public void setEndPoint( String endPointIn ) {
        endPoint = endPointIn ;
    }

    public void execute( QueueableContext context ) {

        String restCalloutBody = restCallout.Body__c ;
        String restCalloutId = restCallout.ID ;

        HTTPResponse response = sendHttpRequest( restCalloutBody ) ;

         // Update the restCallout.
        Rest_Callout__c restCallout = [Select ID, Response__c, Send_Counter__c, 
               SendDateTime__c From Rest_Callout__c Where ID = :restCalloutId] ;
        restCallout.Send_Counter__c++ ;
        restCallout.SendDateTime__c = Datetime.now() ;

        if ( response == null )
        {
            restCallout.Response__c = null ;
        }
        else
        {
            restCallout.Response__c = response.getBody() ;
        }
        update restCallout ;
    }

    public HTTPResponse sendHttpRequest( String restCalloutBody )
    {
        HTTPResponse response = null ;
        String hsdpUrl = '' ;

        try
        {
            // Construct the HTTPRequest.
            HTTPRequest request = new HTTPRequest() ;
            request.setEndpoint( 'callout:' + endPoint ) ;
            request.setMethod( 'POST' ) ;
            request.setHeader( 'Accept', 'application/json' ) ;
            request.setHeader( 'Content-Type', 'application/json' ) ;
            request.setBody( JSON.serialize( restCalloutBody ) ) ;

            // Send the request.
            HTTP http = new HTTP() ;
            response = http.Send( request ) ;
        }
        catch ( Exception e )
        {          
            System.debug( 'Error sending observation to HSDP due to: ' + e ) ;
        }

        return response ;
    }
}

OutboundMessageSenderMockService

@isTest
global class OutboundMessageSenderMockService implements HttpCalloutMock
{

    global HTTPResponse respond( HTTPRequest request )
    {
        System.assertEquals( request.getEndpoint(), 
                             'https://hsdp.server.com/patient' ) ;
        System.assertEquals( request.getMethod(), 'POST' ) ;

        HTTPResponse response = new HTTPResponse() ;
        response.setHeader( 'Content-Type', 'application/json' ) ;
        response.setBody( '{ "Message" : "Success" }' ) ;
        response.setStatusCode( 200 ) ;

        return response ;
    }
}

OutboundMessageSenderTest

@isTest
public Class OutboundMessageSenderTest
{
    @isTest
    static void testOutboundMessageSenderExecute()
    {
        Rest_Callout__c restCallout = insertRestCallout() ;

        ecc_generateTestData.generateConfiguration( false ) ;

        OutboundMessageSender messageSender = new OutboundMessageSender() ;
        messageSender.setRestCallout( restCallout );
        messageSender.setEndPoint( 'IBE_Observation_Test' ) ;

        Test.setMock( HttpCalloutMock.class, new          
                      OutboundMessageSenderMockService() ) ;

        Test.startTest() ;
        ID jobID = System.enqueueJob( messageSender ) ;
        Test.stopTest() ;

        Rest_Callout__c postExecuteRestCallout = 
          [Select Response__c From Rest_Callout__c Where ID = :restCallout.ID] ;
        String responseBody = postExecuteRestCallout.Response__c ;

        System.assertEquals( '{ "Message" : "Success" }', responseBody ) ;
    }

    static private Rest_Callout__c insertRestCallout()
    {
        // Construct the RestCallout instance for this test.
        Rest_Callout__c restCallout = new Rest_Callout__c() ;
        restCallout.Body__c = 'something to send somewhere over the rainbow' ;
        restCallout.Send_Counter__c = 0 ;
        restCallout.SendDateTime__c = Datetime.now() ; 

        insert restCallout;

        return restCallout ;
    }
}

Best Answer

I believe you're looking at a Platform bug - as far as I can tell, setMock is not working for callouts in Queueable Apex classes at this time. I've filed a case and am waiting to hear back.

Update 8/1/2015 - This has been confirmed as a platform bug.

Related Topic