Andy in the Cloud

From BBC Basic to Force.com and beyond…

Code Coverage for WSDL2Apex Generated Classes

20 Comments

Force.com provides a means to generate Apex classes that allow the calling of a given Web Service as described by a WSDL (Web Service Definition Language). This tool is often referred to as the WSD2Apex tool. Despite not having any real “logic” in them, these classes also need code coverage in order to deploy to Production or include a Package.

While the tests for your Apex code that calls the Web Service indirectly ensures you obtain a certain amount of coverage of these classes, it may not be enough. Since you may only require use of a subset of the types and methods generated. The solution is often to comment out the bits not needed, however this is less than ideal if you plan on regenerating the Apex classes, when the Web Service is updated.

This short blog illustrates a way to generically cover the types and methods generated by the WSDL2Apex tool. Such that you don’t need to modify the generated code and can freely update it as desired, adding or removing types or methods in your test class accordingly. It utilises the UPS Street Address Web Service as per this Stack Exchange question, it requires a small tweak to the WSDL before doing so.

Step 1. Covering Generated Types. Each of the inner classes generated represents the data types from the WSDL (sometimes these are split into separate Apex classes). While they don’t have any methods in them, they do have static initialisation code. Constructing each of these classes will execute this logic and generate coverage.

For each of these in the generated class….

public class wwwUpsComXmlschemaXoltwsCommonV10 {
    public class TransactionReferenceType {
        public String CustomerContext;
        public String TransactionIdentifier;
        private String[] CustomerContext_type_info = new String[]{'CustomerContext','http://www.w3.org/2001/XMLSchema','string','0','1','false'};
        private String[] TransactionIdentifier_type_info = new String[]{'TransactionIdentifier','http://www.w3.org/2001/XMLSchema','string','0','1','false'};
        private String[] apex_schema_type_info = new String[]{'http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0','true','false'};
        private String[] field_order_type_info = new String[]{'CustomerContext','TransactionIdentifier'};
    }

Create a test class and test method to cover the type inner classes, repeating line 6 for each.

@IsTest
private with sharing class wwwUpsComWsdlXoltwsXavV10Test
{
	private static testMethod void coverTypes()
	{
		new wwwUpsComXmlschemaXoltwsCommonV10.TransactionReferenceType();
	}
}

Step 2. Covering Generated Methods. Each of the methods on the Port inner class represents the operations described in the WSDL. Fortunately these methods do not care much about the data flowing in or out of them, which makes it easier to create a generic Web Service mock implementation.

For each of these in the generated class methods, observe the types used on lines 9 and 10.

public class wwwUpsComWsdlXoltwsXavV10 {
    public class XAVPort {
        public wwwUpsComXmlschemaXoltwsXavV10.XAVResponse_element ProcessXAV(
                wwwUpsComXmlschemaXoltwsCommonV10.RequestType Request,
                String RegionalRequestIndicator,
                String MaximumCandidateListSize,
                wwwUpsComXmlschemaXoltwsXavV10.AddressKeyFormatType AddressKeyFormat)
        {
            wwwUpsComXmlschemaXoltwsXavV10.XAVRequest_element request_x = new wwwUpsComXmlschemaXoltwsXavV10.XAVRequest_element();
            wwwUpsComXmlschemaXoltwsXavV10.XAVResponse_element response_x;
            // ... WSDL2Apex generated code removed for brevity ...
            return response_x;
        }
    }
}

Then create the following inner class in your test and repeating lines 11 and 12.

@IsTest
private with sharing class wwwUpsComWsdlXoltwsXavV10Test
{
	private class WebServiceMockImpl 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)
		{
			if(request instanceof wwwUpsComXmlschemaXoltwsXavV10.XAVRequest_element)
				response.put('response_x', new wwwUpsComXmlschemaXoltwsXavV10.XAVResponse_element());
			return;
		}
	}
}

Create a test method to cover the generated methods, for each of the methods in generated code repeat line 6 for each. Note that you don’t need to worry about the values being provided to the methods, as the Web Service mock does nothing with them at all. Note that the test context still limits the number of callouts to 10 per test method, so you may need to split the method calls across two test methods.

@IsTest
private with sharing class wwwUpsComWsdlXoltwsXavV10Test
{
	private static testMethod void coverMethods()
	{
		new wwwUpsComWsdlXoltwsXavV10.XAVPort().ProcessXAV(null, null, null, null);
	}
}

Summary. If you want to see a full example of this type of test check out this test class based on the Salesforce Metadata API Web Service. This approach may not be for everyone, certainly if you are already covering a large portion of the generated code or prefer to just delete / comment out the code you don’t need it. However if your providing some kind of connector library or you just want to retain the ability to upgrade the Web Service more easily, or your just determined to keep your 100% code coverage, this might help!

20 thoughts on “Code Coverage for WSDL2Apex Generated Classes

  1. How do you call the generated class to pass to UPS street level validation after you generate the apex classes?

    • It is a standard Apes call, you create an instance of it and call its methods passing the required parameters. Just as if it was an Apex class you have written yourself.

  2. There should be some methods on the generated class you can call that represent the web service operations from the WSDL. Such as the ticketInformationRFtoJtrac method. Simply create an instance of the class and call the method with the correct parameters. If their is any need to pass login information some web services require this to be passed via SOAP Headers, the generated code should have generated some member variouables on the class to set these before you call the methods. Hope this helps!

  3. Something like this…

    CreateTicket.TicketInformationRFtoJtracPort service = new CreateTicket.TicketInformationRFtoJtracPort();
    CreateTicket.CreateTicket ticket = service.ticketInformationRFtoJtrac(clientid, incident, assignto, status, description, resolution, notes, attachment, priority, category, jtracstatus, duedate, urgency, account, fixtype, rootcause);

    • I’m not familiar with RemedyForce so cannot help you with this. You may have to ask them directly or try a more general forum like Salesforce StackExchange. Sorry.

  4. If its a native application on Force.com, you don’t need to make a web service call at all then, just call the Apex classes directly?

  5. You have to use @future, Queable or Batch Apex to perflrm the call out in another async apex exection. Check the apex developers guide for more

  6. I just responsed to your earlier question on this, hope it helps, also this is not a general forum for questions, the post you have commented on is about code coverage for tests. You may find the salesforce forums and/or salesforce stackexchange useful as well.

  7. See my earlier responses

  8. Ha no worries, also depending on my work i am sometimes not the fastest to reply and the Salesforce community is super responsive collectively! 🙂

  9. Hi Andrew,

    Below is my Batch Class created in 2011 Year, Please let me know about what this class doing.

    global class SubmitChecksBatch implements Database.batchable,Database.AllowsCallouts, Database.Stateful{

    global Case cs;
    global List toUpdateFulfillment = new List();
    global List callLogs = new List();
    global List failedCalls = new List();

    global Boolean exceptionOccurred = false;
    global String exceptionMessage;
    global String exceptionTypeName;

    global Database.QueryLocator start(Database.BatchableContext info){
    String query = ‘Select Name, Case__c, Fulfillment_Type__c, Fulfillment_Workflow__c from Fulfillment_History__c’;
    query += ‘ where Fulfillment_Status__c = \’Pending Request\’ and Fulfillment_Type__c in (\’Check\’)’;
    if(UtilityClass.isBatchTestMethodExecution)
    query += ‘ order by createddate desc limit 1’;
    return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext info, List toSubmitChecks){
    try {
    schemasDatacontractOrg200407Telerx.ClientCredentials clientCred = UtilityClass.getClientCredentials();
    tempuriOrg.BasicHttpBinding_ITlxCheckAndLetterProcessor processVar = new tempuriOrg.BasicHttpBinding_ITlxCheckAndLetterProcessor();
    schemasDatacontractOrg200407Telerx.SubmitCheckResponse submitCheckResponse = new schemasDatacontractOrg200407Telerx.SubmitCheckResponse();

    //Create the array of check requests
    schemasDatacontractOrg200407Telerx.ArrayOfCheckRequest chqReqArray = new schemasDatacontractOrg200407Telerx.ArrayOfCheckRequest();
    chqReqArray.CheckRequest = new List();

    Fulfillment_History__c fh = toSubmitChecks[0];

    // Get the Case information from the Fulfillment History record
    Case[] cases = [Select CaseNumber, RecordTypeId, Job__c, Product__c, Current_Fulfillment_Status__c, Fulfillment_Date__c, Claim_Status__c, Disposition__c, OwnerId, Calculated_Rebate__c, MEC_Transaction__c, Tax_ID__c,
    Account.Name, Practice_Address__c, Practice_Address_2__c, Practice_City__c, Practice_State__c, Practice_Zip__c,
    Patient_First_Name__c, Patient_Last_Name__c, Patient_Claim_Address_1__c, Patient_Claim_Address_2__c, Patient_Claim_City__c, State_of_Residence__c, Patient_Claim_Zip__c,
    Primary_Insured_First_Name__c, Primary_Insured_Last_Name__c, Primary_Insured_Address_1__c, Primary_Insured_Address_2__c, Primary_Insured_City__c, Primary_Insured_State__c, Primary_Insured_Zip__c from Case
    where Id = :fh.Case__c];

    cs = cases[0];
    String programCode;
    String caseTypeName;

    // Get the corresponding Job information for the Check and Letter Service Codes
    Jobs__c[] clsCodes = [Select Check_And_Letter_Service_Program_Code__c, Check_Type_Code__c from Jobs__c
    where Id = :cs.Job__c];

    Jobs__c clsCode = clsCodes[0];
    programCode = clsCode.Check_And_Letter_Service_Program_Code__c;

    // Create the Check Request to submit
    schemasDatacontractOrg200407Telerx.CheckRequest chqReqTemp = new schemasDatacontractOrg200407Telerx.CheckRequest();

    chqReqTemp.CheckRequestID = fh.Name;
    chqReqTemp.CheckType = clsCode.Check_Type_Code__c;
    chqReqTemp.CheckAmount = String.valueOf(cs.Calculated_Rebate__c);

    // Set the address of the correct Fulfillment Workflow type
    if(fh.Fulfillment_Workflow__c == ‘Provider’){
    chqReqTemp.MailingAddressLine1 = cs.Practice_Address__c;
    chqReqTemp.MailingAddressLine2 = cs.Practice_Address_2__c;
    chqReqTemp.MailingAddressCity = cs.Practice_City__c;
    chqReqTemp.MailingAddressState = cs.Practice_State__c;
    chqReqTemp.MailingAddressPostalCode = cs.Practice_Zip__c;
    chqReqTemp.MailingName = cs.Account.Name;
    chqReqTemp.PayeeName = cs.Account.Name;

    // Create the array of custom fields
    schemasDatacontractOrg200407Telerx.ArrayOfCustomField customFieldArray = new schemasDatacontractOrg200407Telerx.ArrayOfCustomField();
    customFieldArray.CustomField = new List();

    // Pass the MEC Transaction ID. If there is no MEC Transaction ID on the Case then pass the Case Number.
    if(cs.MEC_Transaction__c != null)
    customFieldArray.CustomField.add(UtilityClass.createCustomWebServiceField(‘MEC_Transaction__c’, cs.MEC_Transaction__c));
    else
    customFieldArray.CustomField.add(UtilityClass.createCustomWebServiceField(‘MEC_Transaction__c’, cs.CaseNumber));

    // Pass the Tax ID if provided
    if(cs.Tax_ID__c != null)
    customFieldArray.CustomField.add(UtilityClass.createCustomWebServiceField(‘Tax_ID__c’, cs.Tax_ID__c));

    chqReqTemp.CustomFields = customFieldArray;

    }
    else if(fh.Fulfillment_Workflow__c == ‘Primary Insured’){
    chqReqTemp.MailingAddressLine1 = cs.Primary_Insured_Address_1__c;
    chqReqTemp.MailingAddressLine2 = cs.Primary_Insured_Address_2__c;
    chqReqTemp.MailingAddressCity = cs.Primary_Insured_City__c;
    chqReqTemp.MailingAddressState = cs.Primary_Insured_State__c;
    chqReqTemp.MailingAddressPostalCode = cs.Primary_Insured_Zip__c;
    chqReqTemp.MailingName = cs.Primary_Insured_First_Name__c + ‘ ‘ + cs.Primary_Insured_Last_Name__c;
    chqReqTemp.PayeeName = cs.Primary_Insured_First_Name__c + ‘ ‘ + cs.Primary_Insured_Last_Name__c;
    }
    else if(fh.Fulfillment_Workflow__c == ‘Patient’){
    chqReqTemp.MailingAddressLine1 = cs.Patient_Claim_Address_1__c;
    chqReqTemp.MailingAddressLine2 = cs.Patient_Claim_Address_2__c;
    chqReqTemp.MailingAddressCity = cs.Patient_Claim_City__c;
    chqReqTemp.MailingAddressState = cs.State_of_Residence__c;
    chqReqTemp.MailingAddressPostalCode = cs.Patient_Claim_Zip__c;
    chqReqTemp.MailingName = cs.Patient_First_Name__c + ‘ ‘ + cs.Patient_Last_Name__c;
    chqReqTemp.PayeeName = cs.Patient_First_Name__c + ‘ ‘ + cs.Patient_Last_Name__c;
    }

    // Add the check request to the array of check requests
    chqReqArray.CheckRequest.add(chqReqTemp);

    // This is used for the test classes since the test classes should not call out to the web service
    if(UtilityClass.isBatchTestMethodExecution) {
    submitCheckResponse.ResponseText = ‘Successful Call’;
    submitCheckResponse.ResponseCode = ‘000’;
    submitCheckResponse.TransactionID = ‘123456789’;
    }
    else {
    submitCheckResponse = processVar.submitChecks(clientCred, programCode, chqReqArray);
    }

    if(submitCheckResponse.ResponseCode == ‘000’) {
    cs.Current_Fulfillment_Status__c = ‘Requested’;
    cs.Fulfillment_Date__c = null;
    fh.Fulfillment_Status__c = ‘Requested’;
    fh.Date_Requested__c = datetime.now();
    update cs;
    toUpdateFulfillment.add(fh);
    }

    Webservice_Call_Result__c callLog = new Webservice_Call_Result__c();
    callLog.Case__c = cs.Id;
    callLog.Name = ‘Submit Check’;
    callLog.Response_Text__c = submitCheckResponse.ResponseText;
    callLog.Response_Code__c = submitCheckResponse.ResponseCode;
    callLog.Transaction_Id__c = submitCheckResponse.TransactionID;
    callLogs.add(callLog);

    // This is used for the test classes so we can test response codes other than ‘000’ for creating a Task
    if(UtilityClass.isBatchTestMethodExecution) {
    submitCheckResponse.ResponseText = ‘Failed Call’;
    submitCheckResponse.ResponseCode = ‘300’;
    submitCheckResponse.TransactionID = ‘123456789’;
    }

    if(submitCheckResponse.ResponseCode != ‘000’) {
    String taskDescription;
    if(submitCheckResponse.ResponseText != null)
    taskDescription += ‘Response Text: ‘ + submitCheckResponse.ResponseText + ‘; ‘;
    if(submitCheckResponse.ResponseCode != null)
    taskDescription += ‘Response Code: ‘ + submitCheckResponse.ResponseCode + ‘; ‘;
    if(submitCheckResponse.TransactionID != null)
    taskDescription += ‘Transaction Id: ‘ + submitCheckResponse.TransactionID + ‘; ‘;

    UtilityClass.sendExceptionEmail(‘SubmitChecksBatch’, ‘Error while submitting checks for CaseNumber ‘ + cs.CaseNumber + ‘. ‘ + taskDescription, ‘Failed Web Service Call’);
    //Task tsk = UtilityClass.createTask(cs, ‘Error while submitting check for the case. Please open a help desk ticket.’, taskDescription, ‘In Progress’, ‘High’, 1, true);
    //failedCalls.add(tsk);
    }
    }
    catch(Exception ex) {
    exceptionOccurred = true;
    exceptionMessage = ‘CaseNumber: ‘ + cs.CaseNumber + ‘ – ‘ + ex.getMessage();
    exceptionTypeName = ex.getTypeName();
    }
    }

    global void finish(Database.BatchableContext info){
    if(toUpdateFulfillment.size() > 0)
    update toUpdateFulfillment;
    if(callLogs.size() > 0)
    insert callLogs;
    if(failedCalls.size() > 0)
    insert failedCalls;
    if(exceptionOccurred)
    UtilityClass.sendExceptionEmail(‘Submit Check’, exceptionMessage, exceptionTypeName);
    }
    }

    • I’m sorry i don’t have time to debug or read your own code, if you have a more specific and focused questioned i can help.

  10. Thanku for the Idea i have got 98% code coverage
    also do we have to write test class for Async class of wsld2 apex

    • Good question, that class started being generated after i wrote this article. The answer will most definitely be yes.

  11. Hi there,

    Thank you very much for the post above.

    I have used the post above to create a test class, and I get 100% coverage of an auto generated wsdl. However when I run it through the force.com security scanner, I get an exception of test method without assert. I have tried to plug in some asserts, but all of the responses seem to come back null.

    Is there an approach that deals with this, or should I not even be worrying about it?

    • I would not worry about this, your only adding them to appease the scanner at this stage. Your could just add system.assertequals that check for null, after all that is the behaviour of most of the classes. Adding full coverage with sample xml is going to be cumbersome and little value. It’s only a low or info level issue as I recall? Maybe just put it in your false positive doc?

      • Thanks for the Reply Andy, I think i’ll take your suggestion and add it in the false positives document. Your blog is awesome by the way I have learned a lot from it.

      • Thank you! Been a little busy relocating to the US from the UK recently, but keen to get back to it soon!

  12. I did this, but cheated/was lazy by having the mock set the response_x to null. Then in the test I surrounded the method call with a catch for null pointer exceptions. Saved having to be concerned matching the incoming request type and response type in the doInvoke() method.

    E.g. The mock…
    private class WebServiceMockImpl implements WebServiceMock {
    public void doInvoke(
    Object stub, Object request, Map response,
    String endpoint, String soapAction, String requestName,
    String responseNS, String responseName, String responseType) {
    response.put(‘response_x’, null); // This will cause a null pointer exception – thats why I have catcher above
    return;
    }
    }

    And the test…

    try { new wwwUpsComWsdlXoltwsXavV10.XAVPort().ProcessXAV(null, null, null, null); } catch (NullPointerException npe) {}

Leave a reply to Manohar Cancel reply