Don’t Use Salesforce’s Apex Test Data Approach

Salesforce has been the center of my professional life since 2011. I’m a big advocate for Salesforce and love much of what they do. However, not their Apex Test Data Approach! Salesforce recommends adding the test data creation to a separate test data factory class using static utility methods. Putting the test data creation in a separate class or classes is good! What is not so good is using static utility methods.

Let’s look at some examples…

TestDataFactory

From the Common Test Utility Classes, they provide this TestDataFactory:

@IsTest
public class TestDataFactory {
    public static void createTestRecords(Integer numAccts, Integer numContactsPerAcct) {
        List<Account> accts = new List<Account>();
        
        for(Integer i=0;i<numAccts;i++) {
            Account a = new Account(Name='TestAccount' + i);
            accts.add(a);
        }
        insert accts;
        
        List<Contact> cons = new List<Contact>();
        for (Integer j=0;j<numAccts;j++) {
            Account acct = accts[j];            
            // For each account just inserted, add contacts
            for (Integer k=numContactsPerAcct*j;k<numContactsPerAcct*(j+1);k++) {
                cons.add(new Contact(firstname='Test'+k,
                                     lastname='Test'+k,
                                     AccountId=acct.Id));
            }
        }
        // Insert all contacts for all accounts
        insert cons;
    }
}

Issues with this approach

  • Not Granular enough – The createTestRecords is doing too much. It’s inserting the requested number of accounts and its requested number of opportunities per account. It’s better to have this more granular such as having two functions with insertAccounts and insertOpportunities with each provided the necessary information.
  • TestDataFactory implies that this single class is where all the code should live for test data creation. This leads to scalability problems where it’ll become a God Class and hard to maintain in the long run.
  • Doesn’t allow one to specify different fields and different values on the Accounts and opportunities.
  • createTestRecords – The function name is too granular. When invoking it, one doesn’t know what data is being created without looking at the function body! The code in this Trailhead is named better at least.

The createObject(Field1, Field2, …, Boolean doInsert) Appoach

Another test data pattern that is advocated by Salesforce is this create<Object> function approach with a doInsert feature parameter. Let’s look at an example that is a slight variant of what’s recommended in this Trailhead:

@isTest
public class TestFactory {

    public static Account insertAccount(String name, String phone, Boolean doInsert) {

        Account acct = new Account(
            Name = name,
            Phone = phone
        );

        if (doInsert) {
            insert acct;
        }

        return acct;
    }
}

This insertAccount takes in a name, phone, and a doInsert as parameters. The name populates the account’s Name and the phone populates the account’s Phone. The doInsert is a Boolean feature parameter that instructs the function whether to insert the account or not.

Issues with this Approach

  • Not Scalable – If one wants to add other fields to be set, this method has to be changed and all the test code that uses it. OR one can create overloads of the function to take in additional fields which can lead to lots of overloaded functions. That doesn’t scale either and is hard to maintain as well.
  • Boolean doInsert – That Boolean feature flag is a bad design! One doesn’t know what it does without looking at the code and it’s not as explicit as it could be. It would be slightly better if the code was split into two functions, buildAccount and insertAccount. buildAccount would take the fields as parameters, build the account with those fields, and then return it. insertAccount would invoke buildAccount, insert it, and then return it.

A Better Approach

The TestDataFactory is a good idea. One should separate their Apex Test Data creation logic into other Test classes so that test data creation is easier to maintain. One should use a Test Class per Object. This helps avoid a Test Factory God Class and makes it easier to maintain overall. The SObject Test Data Framework has worked well for me for years. Another option is the SObjectFabricator Framework from Matt Addy that allows one to mock their Object records without actually inserting. Matt mentioned it’s good for unit tests but integration tests are important too.

For a more in-depth discussion, check out my Salesforce Ben Apex Testing Essentials Course.