21

I have a mock class for a web service that implements WebServiceMock.

With some requests the actual web service being mocked will return a SOAP Fault that results in a System.CalloutException in Apex. E.g. passing in an invalid external ID.

How can I mock the SOAP fault response that results in a CalloutException?

I have code that catches these exceptions that I'd like to test using the mock. I'd prefer not to insert special test cases in the actual code using Test.isRunningTest() to simulate the response.

A sample response from the web service looks like:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <s:Fault>
         <faultcode>s:Client</faultcode>
         <faultstring xml:lang="en-US">Validation Error(s) occurred during Foo Get.</faultstring>
      </s:Fault>
   </s:Body>
</s:Envelope>

I cannot throw a new instance of System.CalloutException. Doing so gives the error:

Type cannot be constructed: System.CalloutException

I cannot extend System.CalloutExceptions. The compiler gives me the error:

LocalCalloutException: Non-virtual and non-abstract type cannot be extended: System.CalloutException

Usually I would put an instance of the class specified in the responseType parameter into the response parameter dictionary with the key 'response_x'. I tried switching this out with a SOAP fault:

response.put('response_x', '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><s:Fault><faultcode>s:Client</faultcode><faultstring xml:lang="en-US">Validation Error(s) occurred during Foo Get.</faultstring></s:Fault></s:Body></s:Envelope>');

This resulted in a (here FooBar_element is the type specified in responseType):

System.TypeException: Collection store exception putting String into MAP<String,FooBar_element>

  • You just have to return a regular soap fault, I think. Generate the appropriate xml for a soap fault, and the platform should translate this into a thrown soap fault in the code being tested. – sfdcfox Jun 24 '14 at 4:26
  • @sfdcfox Usually I would put an instance of the class specified in the responseType parameter into the response parameter dictionary with the key 'response_x'. Short of throwing an exception that dictionary seems to be the only output mechanism. I'll see if I can put a SOAP fault into it. – Daniel Ballinger Jun 24 '14 at 8:53
  • Can you add FooBar_element.class? – Torsen Aug 18 '15 at 12:50
  • @Torsen FooBar_element here will be an arbitrary inner apex class that Wsdl2Apex generated for the SOAP response. It will have members to hold the data, plus a corresponding membername_type_info String[]. There will also be string[] for apex_schema_type_info and field_order_type_info. That's about it. There is no inheritance involved. – Daniel Ballinger Aug 18 '15 at 20:03
18
+50

It's possible to scaffold a System.CalloutException by deserializing it into existence or instantiating it with the type. Then you can throw it in your mock:

Adrian Larson notes that as of API v36.0 you can just construct them :-)

@TestVisible class ResponseMock implements WebServiceMock {
    public void doInvoke(
        Object stub,
        Object request,
        Map<String, Object> response,
        String endpoint,
        String soapAction,
        String requestName,
        String responseNs,
        String responseName,
        String responseType
    ) {
        CalloutException e = new CalloutException();
        e.setMessage('Validation Error(s) occurred during Foo Get.');
        throw e;
    }
}

Inject it into your test with the normal HTTP mock method:

Test.setMock(WebServiceMock.class, new ResponseMock());
client.doTheCallout();
  • 4
    Brilliant! I wonder what other troublesome classes can be constructed that way? – Daniel Ballinger Feb 21 '16 at 19:22
  • 3
    Wow...I thought it was impossible to throw most standard Exceptions. Mind = blown. – Adrian Larson Feb 22 '16 at 0:13
  • It looks like the system is actually allowing the empty constructor, so no need for casting. I wonder if they loosened this up recently? – Adrian Larson Feb 22 '16 at 0:17
11

Just extending the findings of @BigAssForce. Not trying to steal any thunder, but this is too long to be a comment.

In Creating Custom Exceptions, the documentation still claims:

Since you can’t throw built-in Apex exceptions but can only catch them, you can create custom exceptions to throw in your methods.

Despite this assertion, the compiler now allows built-in Exception types to be constructed. I confirmed through Execute Anonymous with the following snippet:

try
{
    CalloutException e = new CalloutException();
    e.setMessage('This is a constructed exception!');
    throw e;
}
catch (Exception pokemon)
{
    system.debug(pokemon);
}

I went through the whole list and what do you know? The following types can all be constructed and thrown:

  • AsyncException
  • CalloutException
  • DmlException
  • EmailException
  • ExternalObjectException
  • InvalidParameterValueException
  • LimitException (though it still can't be caught)
  • JSONException
  • ListException
  • MathException
  • NoAccessException
  • NoDataFoundException
  • NoSuchElementException
  • NullPointerException
  • QueryException
  • RequiredFeatureMissingException
  • SearchException
  • SecurityException
  • SerializationException
  • SObjectException
  • StringException
  • TypeException
  • VisualforceException
  • XmlException
0

I guess all you need to do is create a custom exception which inherits System.CalloutException. System exception cannot be thrown but their sub classes can.

  • 5
    Nice idea. I tried extending System.CalloutException. It won't compile: LocalCalloutException: Non-virtual and non-abstract type cannot be extended: System.CalloutException – Daniel Ballinger Jun 24 '14 at 1:58
  • Sorry, didn't realize that issue. I tried but didn't figure out a way solving that.. – Lance Shi Jun 24 '14 at 4:47

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.