10

I've successfully implemented Named Credentials for a Basic Auth integration callout.

However, when I went to write my test method, I noticed one minor annoyance.

When I try to create a HttpCalloutMock class with the Named Credentials syntax,

req.setEndpoint('callout:My_Named_Credential/' + uri;);

I get an exception:

System.StringException: unknown protocol: callout

Also, as far as I can tell (based on the current documentation), there doesn't seem to be any way to programmatically access the properties of the Named Credential.

So, in order to set my HttpCalloutMockendpoint, I now need to hardcode the URL I entered (or put it in a custom label).

Obviously, this added dependency is not idea for maintenance.

Update

Even more frustrating... it turns out the exception is still thrown even after hardcoding the endpoint in the HttpCalloutMock.

7
  • also would have made a lot more sense if they did this as an actual protocol, i.e. "callout://My_Named_Credentials/some_path", if it's a valid url that seems like it would play nicer with other methods, as opposed to "callout:My_Named_Credentials/some_path", which is only a valid URI which doesn't make sense for a callout May 7, 2015 at 13:58
  • @RalphCallaway Or allowed you to actually pass the NCred object into a specific method on the request, or better, just access the data programmatically. I don't foresee this feature being useful for anyone who has strayed away from the basic auth (for example HMAC signatures)
    – NSjonas
    May 7, 2015 at 16:05
  • it just means it would be more likely that other internal things don't misbehave, i.e. "Not a valid protocol", since there isn't a protocol in "callout:My_named_credential". Underneath the covers there a ton of java code intended to work with URLs so if the named credential was actually a URL I suspect there would be less annoying gotchas like you've encountered May 8, 2015 at 17:03
  • and yeah, seems like it would have made more sense to have the named credential not be endpoint specific, but instead have you set the endpoint and then .setCredential() on the request May 8, 2015 at 17:51
  • @RalphCallaway Making it endpoint specific does increase the security. It can only be sent to the endpoint it specified for. If SF is comprised, then they could post the username and password to their own system
    – NSjonas
    May 8, 2015 at 17:54

2 Answers 2

9

Ahh, new Salesforce features.

That said, if you're mocking, you're not really testing any of that functionality, just how the response is handled. You're also not calling a real server, so the endpoint doesn't really matter. I'm never a fan of having to do hoops around limited Test functionality, but seems like we can make do in the interim.

So for the callout class, just change the endpoint to use or not use the named credential if you're running a test. If the replacement URL is deterministic, you can still create mock classes that work on multiple endpoints. And no hardcoding required ...

String namedCredential = 'My_Named_Credential';
String path = '/some/path';
String uri;
if(Test.isRunningTest()) {
  // would probably want to put a method for this in a TestUtil so you could get
  // to and from a mock path deterministically from a namedCredential/path combo
  uri = 'http://callout.' + namedCredential + '.com' + path; 
} else {
  uri = 'callout:' + namedCredential + path;
}
HttpRequest req = new HttpRequest();
req.setEndpoint(uri);
2
  • 4
    Side Note: Use a Ternary operator instead of an if/else. You'll get 100% Code Coverage. String uri = Test.isRunningTest() ? 'http://callout.' + namedCredential + '.com' + path : 'callout:' + namedCredential + path; Jul 10, 2015 at 14:57
  • 2
    Also, if you use the MultiStaticResourceCalloutMock version, it does seem to let you set an endpoint like multimock.setStaticResource('callout:Google_Analytics...') Jan 18, 2018 at 20:39
0

Not quite an answer to the original question, but an alternative solution I use for testing external integrations is setting up a single MockHttpGenerator as follows...

This enables me to use a mix of named credentials and regular URLs as needed; just need to make sure your named credential's name also exist in URL. i.e. callout:TaxJar_API vs api.taxjar.com

@isTest
public class MyMockHttpResponseGenerator implements HttpCalloutMock {
    public HTTPResponse respond(HTTPRequest req) {
        HttpResponse res = new HttpResponse();

        /**
         * Support Various APIs
         */

        // TaxJar API
        if (req.getEndpoint().toLowerCase().contains('taxjar')) {

            // All responses are in JSON format
            res.setHeader('Content-Type', 'application/json');

            // Assume requests are successful, unless changed below
            res.setStatusCode(200);
            
            // Categories
            if (req.getEndpoint().contains('/categories')) {
                
                // Mock response from /categories endpoint
                res.setBody('{"categories": [' +
                    '{' +
                        '"name": "Installation Services", ' +
                        '"product_tax_code": "19000", ' +
                        '"description": "Installation services separately stated ' +
                                        'from sales of tangible personal property."' +
                    '}' +
                ']}');
            }
        }

        // Return generic response
        else {
            res.setBody('{"foo": "bar"}');
        }

        // Return mock response
        return res;
    }
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .