23

I need to setup test data in proprietary cloud platform (Salesforce.com) where I have no access to advanced testing frameworks. Besides that you can read my code example like it would be Java.

Setting up data basically means to insert records of a quite complex object structure into the database. I decided to go with the Test Data Builder pattern described in this blog post and the related book.

It took me a while to realize that this makes everything much harder as you have to decide where to put your actual DML (meaning when do you insert the record you build).

If you do it naively in the build() I see no way to optimize / bulkify DML statements - which would be very important at the Salesforce.com platform.

On the other hand if you pass around Builder instances instead of Record instances around and build everything in the end you need extra methods to pass (in-memory but no yet persisted records) around.

@isTest
public class OpportunityBuilder {

  private Account parentAccount
    public Opportunity record;
    private List<LineItemBuilder> lineItemBuilders;


  public OpportunityBuilder(Account acc) {
    record = new Opportunity();
    record.Name = ...
    record.Account = acc.Id; 
    ...
    lineItemBuilders = new List<LineItemBuilder>();
  }


  public OpportunityBuilder add(LineItemBuilder lib) {
    lineItemBuilders.add( lib );
    return this;
  }


  public Opportunity build() {
    insert record;

    List<LineItem> lis = new List<LineItem>();
    for(LineItemBuilder builder : lineItemBuilders) {
        LineItem li = builder.record;
        li.Opportunity = record.Id;  // Id requires previous DML
        lis.add(li);
    }
    insert lis;

    return record;
  }
}

My question now is:

  1. How can I make my Builder classes super-extensible? Delay DML and pass Builders instead of records?
  2. Do you know of any resource which enhances the above pattern for database use?
  3. What other solutions do you know that would be an alternative?
10
  • 1
    If you ask me, this method (using describe calls to manufacture unit test data) smashes this technique out of the park. I just started using it 2 days ago, and I'm sold. github.com/mbotos/SmartFactory-for-Force.com Commented May 26, 2015 at 8:46
  • 1
    Hmmm that's true - it's a bit of a black box and I hadn't considered the problem that this could cause. I'll have to take a close look at wether it's going to work for me, as my current project has a complicated data structure (50+ custom objects). Commented May 26, 2015 at 9:04
  • 3
    You can wrap the SObject Hierarchy structure in a set of custom Apex Classes for your Unit Tests. The custom Apex Classes would hold instance variables for the SObject it wraps, a fakeId generated by the Apex Class, and several collections of children objects, which are all wrapped by the Apex Classes. After your builder has completed building its test data set you then have a seperate method which would parse the Apex Classes and do the DML once everything has been completely setup. I have done that for incredibly complex financial engines in Apex where clicks wouldn't work, and it huge tests Commented May 27, 2015 at 22:11
  • 2
    So that approach requires, as you noted in your question, that you need to write some seperate DML handling methods. However that tradeoff makes it much easier to setup the many permutations of data you need to unit test for complex hierarchical. Commented May 27, 2015 at 22:21
  • 4
    Wondering if Unit Of Work can help here, its great at stiching things together? andyinthecloud.com/2014/07/17/… Commented Jun 12, 2015 at 17:30

1 Answer 1

10

UPDATE: A much improved and real-life tested version can be found in this GitHub repo: https://github.com/rsoesemann/apex-domainbuilder

Thanks to Andrew Fawcett and his amazingly powerful UnitOfWork implementation I was able to create a base class to create amazingly simple Builder classes per Domain object.

Those not only have the general benefits of TestDataBuilders over Helper classes but also:

  • Care about the DML as pure In-Memory tests are harder on Force.com
  • Take care of bulkification and Governor Limits
  • Could become an new Force.com Enterprise pattern always using the singular SObject names for custom objects

I'm planning on writing a blog post giving more code examples but I try to show the basics here.

Base class

public virtual class DomainObject {
    
    private static fflib_SObjectUnitOfWork uow = initUnitOfWork();
    public SObject record;
    
    public DomainObject(SObject standalone) {
        register(standalone);
    }
    
    
    public DomainObject(SObject child, Schema.SObjectField parentRelationship, DomainObject parent) {
        this(child);
        register(parentRelationship, parent.record);
    }
    
    
    public DomainObject(SObject linker, 
                        Schema.SObjectField leftRelationship, DomainObject left, 
                        Schema.SObjectField rightRelationship, DomainObject right) {
                            this(linker, leftRelationship, left);
                            register(rightRelationship, right.record);
                        }
    
    public DomainObject persist() {
        uow.commitWork();
        
        uow = initUnitOfWork();
        
        return this;
    }
    
    protected DomainObject addChild(DomainObject child, Schema.SObjectField parentRelationship){
        uow.registerRelationship(child.record, parentRelationship, record);
        return this;
    }
    
    private void register(SObject record) {
        this.record = record;
        
        if(record.Id == null) {
            uow.registerNew(record);
        }
    }
    
    private void register(Schema.SObjectField relationship, SObject relatedRecord) {
        if(relatedRecord.Id == null) {
            uow.registerRelationship(record, relationship, relatedRecord);
        }
        else {
            record.put(relationship, relatedRecord.Id);
        }
    }
    
    private static fflib_SObjectUnitOfWork initUnitOfWork() {
        return new fflib_SObjectUnitOfWork(new List<Schema.SObjectType>{ 
            Grandfather__c.SObjectType,
                Father__c.SObjectType,
                Child__c.SObjectType
                });
    }
}

Domain object with father and child records:

public class Father extends DomainObject {
    
    private Father__c fht;
    
    // CONSTRUCTORS
    
    public Father(Parent par) {
        super(new Father__c(), Father__c.mdr_Parent__c, par);
        
        fht = (Father__c) record;   
        fht.txt_Field1__c = 'default';
        //...
    }
    
    public Father() {
        this(new Grandfather());
    }
    
    // BUILDER METHODS
    
    public Father add(Child chl) {
        return (Father) addChild(chl, Child__c.lkp_Father__c);
    }
    
    public Father addChild() {
        return (Father) add(new Child());
    }
    
    //...many other builder methods
}

Usage in an integration test:

@isTest
private class MyApp_Test { 
    
    @isTest
    private static void worksAsDefined() {
        
        // Setup 
        Father bob = new Father()
                            .age(45)
                            .job('Clerk')
                            
                            .add(new Child()
                                        .age(22)
                                        .gender(MALE)
                                        .hobby(BASEBALL)
                            )
                            .persist();

        // Exercise 
        doThisAndThat(bob.record);
          
        // Verify 
        System.assert(allIsGood());

...

5

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .