10

I have a class that uses a field set to build out a dynamic query. This fieldset is passed into the class.

Is there anyway I can create a test fieldset in my apex code so I don't have to hardcode anything?

This is an example of what my test looks like. init() uses the x_fieldset property to load fields with dynamic SOQL.

@isTest
private static void test(){
    Account acc = TestUtil.createAccount();
    PageController ctrl = new PageController();
    ctrl.fieldSet = SObjectType.Account.fieldsets.MyFieldSet.getFields(); //hardcoded
    ctrl.objId = acc.id;

    test.startTest();
    ctrl.init();
    test.stopTest();
    //init loads all the fields from ctrl.fieldset into ctrl.obj using ctrl.id
    for(Schema.FieldSetMember f : ctrl.fieldSet) {
        System.assertEquals(ctrl.obj.get(f.getFieldPath()), acc.get(f.getFieldPath()));
    }

}
  • Are the field sets definitely not available? Since they're configuration I'd expect them to be visible to the test method. – Matt Lacey Jul 26 '13 at 1:40
14

Its not possible to create a Fieldset within a test to avoid hard coding. Nor is it possible to dynamically create Schema.FieldSetMember (I've just tried!). So with that confirmed, here are a couple of options...

Conditional Test Execution. Based on Dynamic Discovery of a Test Fieldset. If you not happy hard coding a reference to a Fieldset (of course this would then get packaged and/or need copying accross as well). You can try to discover the approprite field set via ...

 Schema.SObjectType.Account.fieldSets.getMap().containsKey 

Then if its not available simply return from the test and effectively allow the test to pass. This might not seem as bad as it sounds, so long as the test runs in your developer/sandbox/packaging orgs thats the main thing. Often tests dependent on org config. can fail for many reasons in subscriber environments anyway.

Mocking Pattern. If your really determined to execute this test under all org contexts this means exploring the use a Mocking pattern to abstract your controller code away from using the Schema.FieldSetMember (org config) its dependent on. This does mean changing your code to suite your test, which some people have an objection to (Apex really needs a better mocking framework). However if done properly your controller code should not see any conditional test logic within in it explicitly.

Start with an abstraction (or fake in Mocking terms) over the system config class we want to mock.

public interface IFieldSetMember
{
    Boolean getDBRequired();
    String getFieldPath();
    String getLabel();
    Boolean getRequired();
    Schema.DisplayType getType();
}

Then change your controller to accept a list of IFieldSetMember instead of the system class version.

public fieldSet List<IFieldSetMember>

You controller code should irrespective of context initialise fieldSet something like this...

ctrl.fieldSet = FieldSetMemberListFactory.construct(
     Schema.SObjectType.Account.fieldSets.getMap().get(fieldSetName));

In your test setup code (ahead of constructing your controller) setup your mock field set (mirrored in design like the existing Test.setMock method) something like this...

FieldSetMemberListFactory.setMock(
    new List<SObjectField> { Account.Name, Account.AnnualRevenue });    

A factory class providing this support might look like this...

public FieldSetMemberListFactory
{
     private FieldSetMemberListFactory() {} 

     private static List<SObjectField> s_mockFieldSetMemberList;

     /** 
      * Constructs a IFieldSetMember list based on the given field set
      **/
     public static List<IFieldSetMember> construct(Schema.FieldSet fieldSet)
     {
          // Return the mock response?
          if(Test.isRunningTest() && s_mockFieldSetMemberList!=null)
              return s_mockFieldSetMemberList;

          // Return the actual response from an actual FieldSet resolved at runtime
          List<IFieldSetMember> fieldSetMemberList = new List<IFieldSetMember>();
          for(System.FieldSetMember member : fieldSet.getFields())
              fieldSetMemberList.add(new RuntimeContextImpl(member));
     }

     /**
      * Provides a mock response to the 'construct' method during a test context
      **/
     @TestVisible
     private static setMock(List<SObjectField> mockFields)
     {
          // Mocks the list of fields via a list of SObjectField's
          s_mockFieldSetMemberList = new List<IFieldSetMember>();
          for(SObjectField field : mockFields)
              s_mockFieldSetMemberList.add(new TestContextImpl(field));
     }

     private class RuntimeContextImpl implements IFieldSetMember
     {
          // Implement the constructor and methods by delegating to Schema.FieldSetMember methods
     }

     private class TestContextImpl implements IFieldSetMember
     {
          // Implement the constructor and methods by delegating to SObjectField.getDescribe result
     }
}
| improve this answer | |
  • perfect... I had considered the mocking pattern, but I wanted to know if there was a simpler option. Good answer. – NSjonas Jul 26 '13 at 19:36

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.