[SalesForce] Testing InvocableMethod

so basically I ripped off http://www.iterativelogic.com/using-invocable-methods-with-skuid/ in order to integrate an automatic Lead Convert Method that can be called from Skuid and I'm unable to test it.

The InvocableMethod is:

global class LeadConvertAction {  
    @InvocableMethod(label='Convert Leads')
    global static List<ConvertLeadActionResult> convertLeads(List<ConvertLeadActionRequest> requests) {
        List<ConvertLeadActionResult> results = new List<ConvertLeadActionResult>();

    try {
      for (ConvertLeadActionRequest request : requests) {
        results.add(convertLead(request));
      }
    }
    catch(Exception ex) {
      System.debug(ex);
    }

    System.debug(results);
    return results;
  }

  public static ConvertLeadActionResult convertLead(ConvertLeadActionRequest request) {
    Database.LeadConvert lc = new Database.LeadConvert();
    lc.setLeadId(request.leadId);
    lc.setConvertedStatus(request.convertedStatus);

    if (request.accountId != null) {
        lc.setAccountId(request.accountId);
    }

    if (request.contactId != null) {
      lc.setContactId(request.contactId);
    }

    if (request.overWriteLeadSource != null && request.overWriteLeadSource) {
      lc.setOverwriteLeadSource(request.overWriteLeadSource);
    }

    if (request.createOpportunity != null && !request.createOpportunity) {
      lc.setDoNotCreateOpportunity(!request.createOpportunity);
    }

    if (request.opportunityName != null) {
      lc.setOpportunityName(request.opportunityName);
    }

    if (request.ownerId != null) {
      lc.setOwnerId(request.ownerId);
    }

    if (request.sendEmailToOwner != null && request.sendEmailToOwner) {
      lc.setSendNotificationEmail(request.sendEmailToOwner);
    }

    try {
      Database.LeadConvertResult lcr = Database.convertLead(lc, true);
      if (lcr.isSuccess()) {
        ConvertLeadActionResult result = new ConvertLeadActionResult();
        result.accountId = lcr.getAccountId();
        result.contactId = lcr.getContactId();
        result.opportunityId = lcr.getOpportunityId();
        return result;
      } else {
        throw new ConvertLeadActionException(lcr.getErrors()[0].getMessage());
      }
    }
    catch(Exception ex) {
      ConvertLeadActionResult result = new ConvertLeadActionResult();
      result.error = ex.getMessage();
      return result;
    }
  }

  global class ConvertLeadActionRequest {
    @InvocableVariable(required=true)
    public ID leadId;

    @InvocableVariable(required=true)
    public String convertedStatus;

    @InvocableVariable
    public ID accountId;

    @InvocableVariable
    public ID contactId;

    @InvocableVariable
    public Boolean overWriteLeadSource;

    @InvocableVariable
    public Boolean createOpportunity;

    @InvocableVariable
    public String opportunityName;

    @InvocableVariable
    public ID ownerId;

    @InvocableVariable
    public Boolean sendEmailToOwner;
  }

  global class ConvertLeadActionResult {
    @InvocableVariable
    public ID accountId;

    @InvocableVariable
    public ID contactId;

    @InvocableVariable
    public ID opportunityId;

    @InvocableVariable
    public String error;
  }

  class ConvertLeadActionException extends Exception {}
}

And then my testClass as of now is:

@isTest
global class LeadConvertActionTest {

    private static testMethod void doTest() {

        Test.startTest();

        Lead l = new Lead(FirstName = 'TestFirst',
            LastName = 'TestLast',
            Street = 'Somewhere',
            City = 'Around',
            State = 'CA',
            PostalCode = '90002',
            Phone = '3103081233',
            Email = 'testing@this.com',
            Existing_PV_System__c = 'Yes',
            Utility__c = 'Other');

        insert l;

        //String jsonInput = '{"leadId": "' + l.Id + '", "convertedStatus": "Closed - Converted"}';
    //String jsonInput = '{"inputs":[{"leadId": "' + l.Id + '", "convertedStatus": "Closed - Converted"}]}';
    //Map<String, Object> leadObj = (Map<String, Object>)JSON.deserializeUntyped(jsonInput);
    //Map<String, Object> leadObj = (Map<String, Object>)JSON.deserializeStrict(jsonInput, String.class);
    //Map<String, String> MyStrings = new Map<String, String>{'leadId' => l.Id, 'convertedStatus' => 'Closed - Converted'};
    //new Object { 'leadId' => l.Id, 'convertedStatus' => 'Closed - Converted' }

    List<LeadConvertAction.ConvertLeadActionRequest> leadC = New List<LeadConvertAction.ConvertLeadActionRequest>();

    //leadC.leadId = new ID l.Id;

    leadC.leadId = l.Id;
    leadC.convertedStatus = 'Closed - Converted';
    List<LeadConvertAction.ConvertLeadActionResult> converted = LeadConvertAction.convertLeads(leadC);
    Test.stopTest();

    Account a = [Select FirstName, LastName, Id From Account where ID = :converted.accountId];
    Contact c = [Select FirstName, LastName, Id From Contact where ID = :converted.contactId];
    Opportunity o = [Select Account.FirstName, Account.LastName, Id From Opportunity where ID = :converted.opportunityId];

    system.assertEquals(a.FirstName, l.FirstName);
    system.assertEquals(c.LastName, l.LastName);
    system.assertEquals(o.Account.LastName, l.LastName);
  }
}

The different json strings and maps were my futile attempts at making the object correct but I keep getting:

I've tried many solutions but can solve it. The last errors I'm getting are:

Error: Compile Error: Initial term of field expression must be a concrete SObject: List<LeadConvertAction.ConvertLeadActionRequest> at line 28 column 9

Now the error is quite obvious, but I'm not sure why it's happening.
I've also try to create the leadC.leadId field as a new ID but a whole different range of errors appear, from cannot be initiated as an ID etc.
Before I tried simply parsing, encoding, creating a List, and passing that directly to the function Call, but was always getting type mismatches so decided to initialize the variables with News and try to set their values and then pass it…

In the real usage, it will be called from a JS snippet as:

var request = '{"inputs":[{"leadId": "' + row.Id + '", "convertedStatus": "Closed - Converted"}]}';
    console.log(request);

    $.ajax('/services/data/v33.0/actions/custom/apex/LeadConvertAction', {
        data: request,
        type: 'POST',
        crossDomain: true,
        dataType: "json",
        beforeSend: function(xhr) {
            xhr.setRequestHeader('Authorization', 'Bearer ' + sforce.connection.sessionId);
            xhr.setRequestHeader('Content-Type', 'application/json');
        },
...

If anyone can help would be greatly appreciated!
Thanks
Micael

Best Answer

The message is somewhat opaque, but your problem is that you are trying to set instance properties on a collection. Think about it, List<ConvertLeadActionRequest> does not have a property named leadId, and if it did, what would it mean? You need to construct an individual instance and set the properties there.

List<LeadConvertAction.ConvertLeadActionRequest> requests =
    new List<LeadConvertAction.ConvertLeadActionRequest>();
// the above does not have a property named leadId

LeadConvertAction.ConvertLeadActionRequest request =
    new LeadConvertAction.ConvertLeadActionRequest();
// the above does have a property named leadId

request.leadId = l.Id;
request.convertedStatus = 'Closed - Converted';
requests.add(request);