11

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 ;
    }
}
4

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.

| improve this answer | |
  • I'm hitting the same issue, did you get any joy with Salesforce, what is your case number i can help support if needed. – Andrew Fawcett Jul 31 '15 at 17:16
  • Hi Andrew. It was just confirmed yesterday as a platform bug and I'm told it should be up on the known issues site next week. Case Number 12293391. You should also be aware of this one: success.salesforce.com/issues_view?id=a1p300000008ZJ0AAM (this month has been a busy one for me in terms of finding platform bugs). – kibitzer Aug 1 '15 at 17:29
  • Thanks most useful, well for us the community, not so i'm guessing for you! ;-) But anyway thanks for reporting and sharing. :-) – Andrew Fawcett Aug 3 '15 at 10:24
  • 1
    Here's the known issue for this one (went up yesterday) success.salesforce.com/issues_view?id=a1p30000000sXtbAAE – kibitzer Aug 4 '15 at 17:28
1

I believe the "System.CalloutException: Callout loop not allowed" CalloutException is intended to stop one synchronous Apex transaction from calling out to another Salesforce hosted web service.

The work around for this was to switch to an asynchronous context such as batch or a future method. See Knowledge Article Number: 000181795 error: "System.CalloutException: Callout loop not allowed".

From your example code it would appear the endpoint isn't to a Salesforce resource, so you should be OK there.

Since that isn't the case, I suspect whatever does the callout loop detection is getting confused by the HttpCalloutMock within the Queueable interface.

A couple of things to try:

  • alter the testing 'IBE_Observation_Test' endpoint to be a more realistic URL. I.e. 'https://hsdp.server.com/patient'
  • Skip over the callout when testing the Queueable interface and directly use the HttpMock implementation rather than making the response = http.Send( request) ; call. I.e. Use Test.isRunningtest() and directly call the OutboundMessageSenderMockService respond method. It will be messy, but you can still test the execute method outside of the System.enqueueJob call.
| improve this answer | |
  • Thanks Daniel. I went with your 2nd suggestion (using Test.isRunningTest()). – Phil Lawrence Feb 23 '15 at 18:46
  • Great, although I was intrigued to know if the first suggestion would have helped if it was being interpreted as a relative URL. If you considered this question answered there is a green check mark. There are also voting buttons to indicate answers you found helpful. – Daniel Ballinger Feb 23 '15 at 20:09

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.