8

I'm building a Queueable that consumes an Azure service bus queue. Every call to the endpoint gives me one message so I keep on calling that same endpoint until I get a 204. The method that calls the endpoint is recursive, so it calls itself if response status code is OK.

My issue is regarding the HttpCalloutMock implementation to unit test it. I need it to just reply OK once and then reply 204. My respond method is:

public HTTPResponse respond(HTTPRequest req) {
  HttpResponse res = new HttpResponse();
  res.setBody(this.body);
  if (count == 0) {
    res.setStatusCode(200);
  } else {
    res.setStatusCode(204);
  }
  count++;
  return res;
}

As you see I try to keep track of the times the mock has been used in the count variable (init as 0 in mock class constructor) but the test fails by System.AsyncException: Maximum stack depth has been reached.. So I remove @isTest from the mock class to get the coverage highlights in Mavensmate, and also do some logging, and both ways I confirm that the mock seems to be used as it was initialized, so I never get a 204 answer that tells the Queueable to stop calling that endpoint.

I thought that MultiStaticResourceCalloutMock was going to help but it is for specifying responses to different endpoints with one mock class only.

Making the count variable static did not work.

I've seen examples where they condition the response to something in the request but I can't do that because I perform the same exact request always.

How can I achieve my desired mock behavior?

  • 1
    Did you try different test methods for each callout? – Sebastian Kessel Jun 16 '17 at 17:06
5

This test works just fine for me:

@IsTest
class Demo
{
    class Mock implements HttpCalloutMock
    {
        Boolean hasResponded = false;
        public HttpResponse respond(HttpRequest request)
        {
            HttpResponse response = new HttpResponse();
            response.setStatusCode(hasResponded ? 204 : 200);
            hasResponded = true;
            return response;
        }
    }
    static testmethod void testMyMock()
    {
        HttpRequest request = new HttpRequest();
        request.setEndpoint('...');
        request.setMethod('GET');

        Test.startTest();
            Test.setMock(HttpCalloutMock.class, new Mock());
            HttpResponse response1 = new Http().send(request);
            HttpResponse response2 = new Http().send(request);
        Test.stopTest();

        system.assertEquals(200, response1.getStatusCode());
        system.assertEquals(204, response2.getStatusCode());
    }
}

The same test passes if you change the mock definition to:

class Mock implements HttpCalloutMock
{
    Integer timesCalled = 0;
    public HttpResponse respond(HttpRequest request)
    {
        HttpResponse response = new HttpResponse();
        response.setStatusCode(timesCalled++ > 0 ? 204 : 200);
        return response;
    }
}
  • Sorry, super wrong analysis from my part, the error was not there and the conditioned mock does indeed work. – Pedro Otero Jun 16 '17 at 20:19
  • Is it ok if I mark this as an answer? My problem was not related to the mock but people keep making comments and posting answers... – Pedro Otero Jun 16 '17 at 20:46
  • @PedroOtero Obviously I don't have a problem with that! I think it answers the question you asked, though it may not have been exactly the right question to uncover what was blocking you. Seems like I was more of a rubber duck than anything. – Adrian Larson Jun 16 '17 at 20:47
2

IMHO This is the way to go. Control what is returned from the test method itself by constructing the response on the fly. No need for multiple or complex mock classes with this

Since question is not a duplicate and the answer is, just copying the answer from sfdcfox

Help on Invokable Apex Test class and @Future callout Apex test Class

Basically, you need to know about how to test HTTP callouts, and how to test future methods.

Once you've gotten that far, you'll see that the unit test would look like:

@isTest class MakeCalloutTest {
    // Simple echo callout class
    // Returns whatever response we tell it to when asked
    class EchoHttpMock implements HttpCalloutMock {
        HttpResponse res;
        EchoMock(HttpResponse r) {
            res = r;
        }
        // This is the HttpCalloutMock interface method
        public HttpResponse respond(HttpRequest req) {
            return res;
        }
    }

    @isTest static void test() {
        // Avoid using live data
        List<Lead> leads = new List<Lead>{ new Lead(LastName='Test',Company='test') };
        insert leads;
        // We tell it what to simulate
        HttpResponse res = new HttpResponse();
        res.setBody('<?xml version="1.0" encoding="utf-8"?><root U_Id="12345"></root>');
        res.setStatusCode(200);
        // This allows the callout to succeed
        Test.setMock(HttpCalloutMock.class, new EchoHttpMock(res));
        // Start the test
        Test.startTest();
        // Enqueue the future call
        MakeCallout.invokeleadcallout(leads);
        // Trigger future method
        Test.stopTest();
        // Verify logic
        leads = [select id__c from lead];
        System.assertEquals('12345', leads[0].Id__c);
    }
}

Note: This doesn't cover the "catch" part of your try-catch block. Once you've gotten as far as covering most of your code, the catch block should be pretty simple (hint: you can construct and throw CalloutException manually from a HttpCalloutMock).

Credit to sfdcfox

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.