5

I have a simple method that I've created as part of an SObjectFactory class. The makeLead() method shown below simply returns a new Lead object.

/**
 * @param  valuesByField
 *             Map of values to be put into specific fields, based on
 *             given field tokens (keys)
 * @return a Lead object with default values and different values
 *         where given through valuesByFieldName
 */
global Lead makeLead(Map<SObjectField, Object> valuesByField) {

    // Initialize the Lead object to return
    Lead record = (Lead)DescribeUtil.getSObjectType('Lead').newSObject(null, true);

    // Populate the record with values passed to the method
    for (SObjectField eachField : valuesByField.keySet()) {
        record.put(eachField, valuesByField.get(eachField));
    }

    // Return the Lead record
    return record;
}

I want to basically repeat this method pattern for every type of SObject I want this factory to support, such as Account, Contact, Opportunity. However, I feel like there should be a way to abstract the logic so that I can effectively just use one method and pass an SObjectType as a parameter.

The problem for me is the line where I'm declaring the record variable, assigning it an object that is casted as (Lead). Is there a way to make this dynamic, based on an SObjectType passed to the method as a parameter?

  • why dont you return an Sobject itself or an Object? – Mohith Shrivastava Jul 19 '15 at 3:02
10

No, there's no way to do a dynamic typecast in apex. However what you can do to work around this is make a virtual or abstract class with all of the logic using the dynamic apex form (e.g. record.put). Then have a subclass for the Lead type that wraps this method's return type in a Lead cast.

For example:

public abstract class GenericFactory{
    public SObject makeRecord(Map<SObjectField, Object> valuesByField) {
        // Initialize the Lead object to return
        SObject record = this.getSObjectType().newSObject(null, true);

        // Populate the record with values passed to the method
        for (SObjectField eachField : valuesByField.keySet()) {
            record.put(eachField, valuesByField.get(eachField));
        }

        // Return the Lead record
        return record;
    }
    public abstract Schema.SObjectType getSObjectType();
}

and a lead-specific subclass:

global class LeadFactory extends GenericFactory{
    public override Schema.SObjectType getSObjectType(){
        return Lead.SObjectType;
    }
    public Lead makeLead(Map<SObjectField, Object> valuesByField){
        return (Lead) this.makeRecord(valuesByField);
    }
}

This is about as clean as you can get without doing the type casts in the calling code.

  • This is exactly what I ended up doing, Chris. Thanks for the input and confirmation about the limits of dynamic casting. In my case I ended up putting all of the methods into a single SObjectFactory class, which has the generic make() method in addition to type-specific methods like makeLead() and makeAccount(). – Marty C. Jul 19 '15 at 22:36
  • Why are you using override? By using abstract in the parent class you are already declaring that this method needs specific implementations. – Matthew Metros Oct 12 at 2:26
2

ca_peterson's example is a great start, however how would you handle changes in org requirements, validation rules, etc that would cause your test factory to fail, and how soon would you know they fail?. Not to mention the code needed to add new object, etc. To each their own.

We start using the SEED for Test methods package both within our test factory and standalone, and it handles all that for use. Never have to remember what fields are require to populate in order to create an object, reducing dev time as the line to create an object is simply obj.createRecord(Account.sObjectType); and everything is taken care of, and if an org change occurs SEED picks it up automatically and the test continue to run without fail.

Not really a direct answer but another potential way to solve the situation.

  • Having the per-SObject subclass helps with this - you can adapt makeLead to do Lead-specific work, while still using GenericFactory.makeRecord as the starting point. – ca_peterson Jul 19 '15 at 21:02
  • @ca_peterson - Agreed, and that was the way we did it for the longest time. But when someone added a validation rule or required field, we never knew until it was time to deploy something (then had to update code). Not to mention it became a lot to maintain with all the objects we have. Having to only remember the single line of code, and not update again, to create an object (a lot more is available if you want more control) has reduced the amount of code and made it extremely easy to write test methods. Not for everyone but SEED takes the Test factory to a whole new level (at least for us) – Eric Jul 19 '15 at 21:46
  • Just another option. Too much to put in comments here and more detail is not really appropriate for this type of forum... – Eric Jul 19 '15 at 21:53
  • What is "SEED"? – Marty C. Jul 19 '15 at 22:35
  • @MartyC. - App on the AppExchange – Eric Jul 19 '15 at 22:37
0

You can use a String param to define the sObjectType for dynamic SObject creation:

    public with sharing class TestDataFactory {

    public static List<Sobject> createRecords(String sObjectName, Integer count) {

        List<Sobject> returnList = new List<Sobject>();

        for(Integer i = 0; i < count; i++) {
            sObject sObj = Schema.getGlobalDescribe().get(sObjectName).newSObject();
            sObj.put('Name', 'test'+i);
            returnList.add(sObj);
        }

        return returnList;
    }
}

Then, in Execute Anonymous:

List<Account> a = (List<Account>) TestDataFactory.createRecords('Account', 100);

for(Account acc : a) {
    System.debug(acc.Name);
}

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.