3

I developed VisualForce page with asynchronous callouts to web service according to link.
It is working fine, my page is showing results after callback is returned. I really like pattern with Continuation object.
But I've got seriuos problems with testing my controller. Provided example doesn't work. Salesforce is complaining that webservice callouts are not supported in test methods.
I checked and testing Continuation with plain HttpRequest/Response works.
Maybe somebody was able to test controller with Continuation?

Let me show you my code. For purpose of this question I will be using public web service http://soaptest.parasoft.com/calculator.wsdl.
VisualForce page:

<apex:page controller="ContinuationController" showHeader="true">
   <apex:form >      
      <!-- Invokes the action method when the user clicks this button. -->
      <apex:commandButton action="{!startRequest}" 
              value="Start Request" reRender="result"/> 
   </apex:form>

   <!-- This output text component displays the callout response body. -->
   <apex:outputText id="result" value="{!result}" />
</apex:page>

Controller:

public with sharing class ContinuationController {
    public AsyncParasoft.addResponse_elementFuture addFuture;
    public String result {get; set;}

    public Continuation startRequest() {    
       Integer TIMEOUT_INT_SECS = 60;  
       Continuation cont = new Continuation(TIMEOUT_INT_SECS);
       cont.continuationMethod = 'processResponse';

       AsyncParasoft.AsyncICalculator calc = new AsyncParasoft.AsyncICalculator();
       addFuture = calc.beginAdd(cont, 2, 5);      

       return cont;   
    }    

    public Object processResponse() {    
       result = String.valueOf(addFuture.getValue());

       return null; 
    }
}

Test class:

@isTest
public class ContinuationTestingForHttpRequest {

    public class elo 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) {
               System.debug('xxx: ' + endpoint);
           }
    }
    public static testmethod void testWebService() {
        ContinuationController controller = new ContinuationController();
        //Test.setMock(WebServiceMock.class, new elo());
        Continuation conti = (Continuation) controller.startRequest(); 
        //above line is giving me the error:
        //Methods defined as TestMethod do not support Web service callouts, test skipped

        String reLabel = 'Continuation-1'; // I checked that this is first label for Continuation
        Map<String, HttpRequest> requests = conti.getRequests();
        system.assert(requests.size() == 1);
        system.assert(requests.get(reLabel) != null);

        HttpResponse response = new HttpResponse();
        response.setBody('6');   
        Test.setContinuationResponse(reLabel, response);
        Object result = Test.invokeContinuationMethod(controller, conti);
        System.assertEquals(null, result);
        System.assertEquals('7', controller.result);
    }
}

As you can see, this is straightforward example. I tried also WebServiceMock, which is giving me Salesforce Internal Error.

Internal Salesforce Error: 104340792-768952 (-1841369010) (-1841369010)

  • I know for testing synchronous call outs, you need to use a class that implements the WebServiceMock interface to simulate the call out. Maybe that'd work for async web services too. salesforce.com/us/developer/docs/apexcode/Content/… – Scott Pelak Mar 25 '15 at 13:32
  • Yeah, I tried using WebServiceMock for async WS, but it results with Salesforce Internal Error – mischczu Mar 25 '15 at 14:24
3

I've just heard back from my support case. This is now a Known Issue - Running Test for WSDL-based asynchronous callouts fails with Internal Salesforce.com Error


I've cross posted this to the developer forums as a bug - WSDL-Based Asynchronous Callouts using Continuations can't be unit tested.

As a short term work around, you can use Test.isRunningTest() strategically within the generated AsyncWwwParasoftComWsdlCalculator class to avoid the actual callouts.

E.g.

public class multiplyResponse_elementFuture extends System.WebServiceCalloutFuture {
    public Double getValue() {
        if(Test.isRunningTest()) { return 7; } // Hard coded Mock Response
        wwwParasoftComWsdlCalculator.multiplyResponse_element response = (wwwParasoftComWsdlCalculator.multiplyResponse_element)System.WebServiceCallout.endInvoke(this);
        return response.Result;
    }
}

//...

    public AsyncWwwParasoftComWsdlCalculator.addResponse_elementFuture beginAdd(System.Continuation continuation,Double x,Double y) {
        wwwParasoftComWsdlCalculator.add_element request_x = new wwwParasoftComWsdlCalculator.add_element();
        request_x.x = x;
        request_x.y = y;

        if(Test.isRunningTest()) {
            // Prevent the actual callout during testing
            return new AsyncWwwParasoftComWsdlCalculator.addResponse_elementFuture();
        }

        return (AsyncWwwParasoftComWsdlCalculator.addResponse_elementFuture) System.WebServiceCallout.beginInvoke(
          this,
          request_x,
          AsyncWwwParasoftComWsdlCalculator.addResponse_elementFuture.class,
          continuation,
          new String[]{endpoint_x,
          'add',
          'http://www.parasoft.com/wsdl/calculator/',
          'add',
          'http://www.parasoft.com/wsdl/calculator/',
          'addResponse',
          'wwwParasoftComWsdlCalculator.addResponse_element'}
        );
    }

You will need to drop the test assertion based on the size of the Continuation.getRequests() Map.


I've found that the WebServiceMock most likely isn't working as the response parameter to doInvoke is null. Without this Map<String, Object> you can't specify the return response.

E.g. The mock should be doing something like:

public void doInvoke(
   Object stub,
   Object request,
   Map<String, Object> response,
   String endpoint,
   String soapAction,
   String requestName,
   String responseNS,
   String responseName,
   String responseType) {

       System.debug(LoggingLevel.INFO, 'eloWsm.doInvoke() - ' +
                                 //'\n stub: ' + stub +
                                 '\n request: ' + request +
                                 '\n response: ' + response +
                                 '\n endpoint: ' + endpoint +
                                 '\n soapAction: ' + soapAction +
                                 '\n requestName: ' + requestName +
                                 '\n responseNS: ' + responseNS +
                                 '\n responseName: ' + responseName +
                                 '\n responseType: ' + responseType);

       wwwParasoftComWsdlCalculator.addResponse_element res = new wwwParasoftComWsdlCalculator.addResponse_element();
       res.Result = 7.0;

       response.put('response_x', res);

   }

However, this is throwing the following exception because response is null:

System.NullPointerException: Attempt to de-reference a null object

The example from Testing WSDL-Based Asynchronous Callouts doesn't make any use of WebServiceMock, so in theory it should work without it.

I've confirmed your findings that leaving it out retults in:

System.TypeException: Methods defined as TestMethod do not support Web service callouts, test skipped

0

An alternative to WebServiceMock is to build the class the way we used to have to do. It's not ideal, but it will work:

public with sharing class ContinuationController {
    public AsyncParasoft.addResponse_elementFuture addFuture;
    public String result {get; set;}
//...
    public Object processResponse() {    
        //only actually do the callout if this isn't a test
        if (!Test.isRunningTest())
            result = String.valueOf(addFuture.getValue());

       return null; 
    }
}

with test class:

@isTest
public class ContinuationTestingForHttpRequest {

    public static testmethod void testWebService() {
        ContinuationController controller = new ContinuationController();

        //set result to be what you want it to be
        controller.result = '{ \'this is the result that will be seen by the controller\' }';

        //...

    }
}

The big concern with this is that the actual webservice callout process isn't being tested the way WebserviceMock does. We are assuming here that your AsyncParasoft class is being tested in its own test class, so that may not be an issue.

  • I've been looking into that but couldn't get it to work with the response types expected by the Continuation. E.g. I couldn't set the AsyncWwwParasoftComWsdlCalculator.addResponse_elementFuture. It only has a public method called getValue(). – Daniel Ballinger Mar 27 '15 at 2:03
  • Can you capture a valid getValue() and then insert that string into your test class? – Jeremy Nottingham Mar 27 '15 at 2:05
0

This is a Known Issue from Salesforce and there is no workaround : https://success.salesforce.com/issues_view?id=a1p30000000sXyr

0

As per documentation, they work only for classes binded with VF page and won't work in other context like in Dev console. I think in test class, we mock this behavior using Test.SetPage() and because of this it is not supported. Salesforce should support mocking for continuation object too.

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.