8

A while ago SF began failing tests that make callouts without an HTTP mock. When they did that we were unable to deploy some trigger as a managed package made a callout whenever an Opportunity was inserted and thus the trigger test failed due to the callout. Unfortunately there was no way to tell the managed package to not make the callout.

I worked with the dev team to add a custom setting to allow us to bypass it.

This question prompted me to revisit the issue: Writing tests for Managed Package

What I did:

  1. Created a managed package: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6A000000V12L

    I had to add a dummy class to it as it would not let me upload with only the test class so ignore those

    Mock Impl Class In package

    @isTest
    global class EchoHttpMock implements HttpCalloutMock {
    
        HttpResponse res;
    
        global EchoHttpMock(HttpResponse r) {
            res = r;
        }
    
        // This is the HttpCalloutMock interface method
        global HttpResponse respond(HttpRequest req) {
            system.debug(res.getBody());
            return res;
        }
    
        global static void setTestMockResponse(Integer statusCode, String status, String body) {
            HttpResponse mockResp = new HttpResponse();
    
            if (statusCode != null)
                mockResp.setStatusCode(statusCode);
    
            if (status != null)
                mockResp.setStatus(status);
    
            if (body != null)
                mockResp.setBody(body);
    
            test.setMock(
                    HttpCalloutMock.class,
                    New EchoHttpMock(mockResp)
            );
    
        }
    }
    

    Managed Callout Class

    global class managed_googleCallout{
    
    
      global static void doIt(){
    
          HTTPRequest req = New HTTPRequest();
          req.setEndpoint('http://www.google.com');
          req.setMethod('GET');
    
          system.debug(New Http().send(req).getBody());
    
    
      }
    }
    

    You can install this package in your org to validate this does not work.

  2. I installed the package in my play org

  3. I created a class that made a callout

    Callout Class

    public class local_googleCallout{
    
    
        public static void doIt(){
    
            HTTPRequest req = New HTTPRequest();
            req.setEndpoint('http://www.google.com');
            req.setMethod('GET');
    
            system.debug(New Http().send(req).getBody());
    
    
        }
    }
    

    Pretty simple (make sure you add the endpoint to remote site settings)

  4. I made a local copy of the EchoHttpMock in the org using the name local_EchoHttpMock

  5. I created a test class

    Local Test Class

    @isTest
    public class googleCalloutTest{
    
    public static testmethod void calloutTestManaged(){
        test.startTest();
    
        mockimpltest.EchoHttpMock.setTestMockResponse(200,'OK','The Body');
    
        local_googleCallout.doIt();
    
        test.stopTest();
    
    }
    
    public static testmethod void calloutTestLocal(){
        test.startTest();
    
        local_EchoHttpMock.setTestMockResponse(200,'OK','The Body');
    
        local_googleCallout.doIt();
    
        test.stopTest();
    
    }
    
    public static testmethod void calloutTestFromLocal_Managed(){
        test.startTest();
    
        mockimpltest.EchoHttpMock.setTestMockResponse(200,'OK','The Body');
    
        mockimpltest.managed_googleCallout.doIt();
    
        test.stopTest();
    
    }
    
    
    }
    

    I ran the test class and the local passes and the managed fails.

    Failing Test

So, how is one supposed to implement a mock implementation from a managed package to support a callout during local test methods? If one wants to supply a testing framework to return particular responses to customers code / implementation it cannot be done.

Salesforce says this is supposed to work but I cannot get it to despite various attempts.

Update

Per Daniels comment, I added a class in the managed package that does a callout. When the mock implementation is implemented locally and the managed method is called the test does pass.

So it seems that only when local code is attempting to implement the callout and the mock implementation is when this occurs.

The initial observation when a trigger caused the callout

Update 3

As long as the callout originates from and the mock are implemented in the same namespace it works:

  • Callout Local - > Mock Managed -> Fail
  • Callout Managed -> Mock Local -> Fail
  • Callout Local -> Mock Local -> Pass (obviously)
  • Callout Managed -> Mock Managed -> Pass

API Version 39.0

  • 1
    We ship a managed package with callouts and have exposed a mock for our implementation team to use and I am fairly certain this is working for them. I will double check and if it is I'll see if there is anything we are doing differently. – dsharrison May 1 '17 at 23:21
  • 1
    Would the mock defined within the managed package work for a callout that originated from within the managed package? I'd also err on the naming of the local mock to distinguish it from the mp mock just in case the namespacing isn't working so well here. – Daniel Ballinger May 1 '17 at 23:35
  • @DanielBallinger - It appears, yes, when executing a managed method and implementing the managed Mock from a local class it does pass. – Eric May 2 '17 at 0:17
  • @DanielBallinger - Seems it is when the local code "causes the callout in the managed package" is the issue. Calling the managed method directly appears to work. – Eric May 2 '17 at 0:31
  • 1
    @Eric, I think the updates you've made to the question could be worked into an answer. Or at least as far as the status quo goes with mocks not working across the managed package boundary. – Daniel Ballinger May 2 '17 at 1:20
2

I may have utterly misunderstood what you're doing here - apologies if I have - but I've regularly use a managed package (that we wrote) with a similar scenario.

Our package is one that we use within the company to simplify making callouts. So, instead of ever directly using

res = httpConnection.send(req);

We call that indirectly using package methods. (The package does things like simplifying error handling and tracking total callout time). I came across the same problem as you with testing and mocks.

In my case, there was no option to have the mock inside the managed package, because the host org is the only one who knows what the mock ought to look like.

It seems to me that Test.setMock works per namespace that it is invoked in, and is independent of where the actual mock is defined (as long as the mock is global).

So, in my managed package I have a method which only exists for inserting a mock into the right namespace:

global static void setMock(HttpCalloutMock theMock) {
    Test.setMock(HttpCalloutMock.class, theMock);
}

Then when I write a test in the host org, I use that instead of directly calling Test.setMock() in my test e.g.

    BrightTalkApiMock mock = new BrightTalkApiMock(settings, 200);

    Nebula_API.NebulaApi.setMock(mock);

Adding modification to Eric's code to clarify my point:

Here's Eric's test, modified to set the mock in the local namespace, but still use the mock defined in the package:

public static testmethod void calloutTestManaged(){
    test.startTest();

    HttpResponse mockResp = new HttpResponse();

    mockResp.setStatusCode(200);

    mockResp.setStatus('OK');

    mockResp.setBody('The Body');

    mockimpltest.EchoHttpMock theMock = new mockimpltest.EchoHttpMock(mockResp);
    Test.setMock(HttpCalloutMock.class, theMock);

    local_googleCallout.doIt();

    test.stopTest();

}

I think this is what you were trying to do, but the use of the static method to make the mock instance was confusingly tying together the definition and the setting of the mock.

  • That's what the above shows does not work. Invoking a managed mock and making a local call out in local code fails with testmethods cannot make callouts – Eric May 2 '17 at 14:23
  • @Eric it does work... I've done it lots of times. I'm trying to figure out what's different about what you're doing... – Aidan May 2 '17 at 14:30
  • i am cheering you on! – Eric May 2 '17 at 14:32
  • @Eric It looks like you have joined together the setting of the mock with the definition of the mock class. If you add a simple method like my setMock(HttpCalloutMock theMock) then you should be able to make it work. Your failing test is failing because the Test.setMock() is being called in a different namespace to that where the callout happens. I'll share more of my code in my answer, to the extent that you could plug it into your tests... – Aidan May 2 '17 at 14:38
  • Thats exactly the point. Invoke a managed mock in the local namespace and make a local callout and it fails. I get it is due to the namespace of the callout vs the namespace of the mock. But if the local sets the mock the local callout should use it IMHO. – Eric May 2 '17 at 14:43

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.