As the Salesforce ISV that develops Nimble AMS, we write many Apex tests to ensure Nimble AMS works as expected under a variety of conditions. As a Salesforce Testing best practice, these tests create all the data they require. Doing so allows the tests to run in any Salesforce org without having data created in the org.

This best practice led us to another best practice: The Apex Data Factory Design Pattern. ​

Data Factories ​

Data Factories are apex classes that are responsible for generating data. They primarily generate Salesforce records in various ways. By far, test classes use Data Factories the most to generate their data. However, they can be used in other contexts also. For example, they can be used to generate data within a Salesforce org. Another example is if you’re a Salesforce ISV  developing a product, your product could have these Data Factories that can then be used within any custom test classes to generate data without replicating the data creation logic.
Alright, enough theory. Let’s see a Data Factory that creates Accounts.

/*
  DataFactory used to generate Accounts. It’s global so that it can be used outside your managed package. The naming convention is “DataFactory{Object_Name}” so that the classes are grouped together allowing one to easily see what Data Factories there are in an org or within the Force.com IDE.
 */
global class DataFactoryAccount {
    // define default constants that can be accessible to assert against.
    global static final String DEFAULT_NAME = ‘ACME Inc.’;

    /* The “create” methods are used to instantiate the sobjects without inserting them. This is tremendously helpful when you need to create a particular record and then tweak it before it’s inserted from the calling code. */
    global static Account createAccount(){
          return createAccount(DEFAULT_NAME);
    }

    /* The create methods are commonly overloaded to allow different properties to be set. */
    global static Account createAccount(String name){
           return new Account(
               Name = name
           );
    }

    /* The “Insert” methods are used to create the sobjects and then insert them. They delegate       the creation to the create methods and then insert the records. Like the create methods, the insert methods are overloaded to allow different properties to be set as necessary. Another best practice is to bulk insert or bulk create data, but to keep the sample short, that’s omitted. */
    global static Account insertAccount(){
          Account accountToInsert = createAccount();

          insert accountToInsert;
          return accountToInsert;
    }
}

Now, let’s see how the DataFactoryAccount can be used.

@isTest
private class TestAccount {

     static testmethod void insertAccountTest(){
         Account company = DataFactoryAccount.insertAccount();

         Account anotherCompany  = DataFactoryAccount.insertAccount(‘Another Company’);

         // Test and assert with Accounts here.
     }

     static testmethod void veryBasicInsertAccountsLoadTest(){
         Integer numberToInsert = 5000;​

         /* Useful to flush out poorly written triggers. For example, a trigger thatdoes SOQL or DML within loops.*/
         List<Account> manyAccounts = DataFactoryAccount.insertAccounts(numberToInsert);
     }
}

Benefits

  • Data creation is factored out of the test classes and contained within their own classes. This allows the logic to be used in a variety of ways.
  • Allows dependent data to be created more easily without necessarily caring about parent records. For example, if an opportunity record needs to be inserted, DataFactoryOpportunity.insertOpportunity can be called and it will automatically insert any other required records and then return the Opportunity record.
  • Reusability. Eliminates the need to copy paste data creation code across the test suite.
  • Reusable in client orgs. With global Data Factories in a managed package, any custom test classes needed for customizations can use them to generate their data without re-creating the same data creation logic.

If you have other uses cases, other ways to implement this, questions or other comments, please leave them below. We’d love to hear from you.
 
Happy Coding,
Luke