Salesforce Apex Test Data Factory

 

Introduction

Writing robust unit tests in Salesforce Apex often means creating a lot of test records for standard and custom objects. If you repeat the same data creation logic in every test class, your code becomes hard to maintain. A Test Data Factory solves this by centralizing your test data creation logic.

In this post, I'll show you how to build a dynamic, reusable Test Data Factory that can create records for any standard or custom object using just the object's API name and a map of field values. This pattern keeps your test code DRY (Don't Repeat Yourself), flexible, and easy to update.


Why Use a Test Data Factory?

  • Maintainability: Change record creation logic in one place.
  • Reusability: Use the same utility for all objects and test classes.
  • Flexibility: Easily add more fields, new objects, or handle edge cases.

The Universal Apex Test Data Factory

Here’s the core of a flexible, universal Apex Test Data Factory:

@isTest public class TestDataFactory { /** * Creates and inserts a single record for any SObject type. * @param objectApiName The API name of the object (e.g., 'Account', 'Contact'). * @param fieldValues Map of field API names to values. * @return The inserted SObject. */ public static SObject createRecord(String objectApiName, Map<String, Object> fieldValues) { SObject record = Schema.getGlobalDescribe().get(objectApiName).newSObject(); if(fieldValues != null) { for (String fieldName : fieldValues.keySet()) { record.put(fieldName, fieldValues.get(fieldName)); } } insert record; return record; } /** * Creates and inserts multiple records for any SObject type. * @param objectApiName The API name of the object (e.g., 'Account', 'Contact'). * @param recordsFieldValues A list of maps, each representing a record's field values. * @return The list of inserted SObjects. */ public static List<SObject> createRecords(String objectApiName, List<Map<String, Object>> recordsFieldValues) { List<SObject> records = new List<SObject>(); for (Map<String, Object> fieldValues : recordsFieldValues) { SObject record = Schema.getGlobalDescribe().get(objectApiName).newSObject(); if(fieldValues != null) { for (String fieldName : fieldValues.keySet()) { record.put(fieldName, fieldValues.get(fieldName)); } } records.add(record); } insert records; return records; } }

How Does This Work?

  • Dynamic Object Creation:
    Uses Schema.getGlobalDescribe() to get the SObject token for any object (standard or custom) by API name.

  • Flexible Field Assignment:
    Pass a Map<String, Object> of field API names to values. The method loops through the map and sets each field.

  • Bulk Support:
    The createRecords method lets you insert lists of records in one go—great for testing batch logic.


How to Use This Test Data Factory

1. Create a Single Standard Object (e.g., Account)

Account acc = (Account) TestDataFactory.createRecord('Account', new Map<String, Object>{ 'Name' => 'Test Account' });

2. Create a Single Contact

Contact con = (Contact) TestDataFactory.createRecord('Contact', new Map<String, Object>{ 'LastName' => 'Smith', 'Email' => 'smith@example.com' });

3. Create Multiple Opportunities

List<Map<String, Object>> oppList = new List<Map<String, Object>>(); oppList.add(new Map<String, Object>{'Name' => 'Opp 1', 'StageName' => 'Prospecting', 'CloseDate' => Date.today()}); oppList.add(new Map<String, Object>{'Name' => 'Opp 2', 'StageName' => 'Proposal', 'CloseDate' => Date.today().addDays(5)}); List<Opportunity> opps = (List<Opportunity>)TestDataFactory.createRecords('Opportunity', oppList);

4. Works for Custom Objects Too!

SObject customObj = TestDataFactory.createRecord('Custom_Object__c', new Map<String, Object>{ 'Name' => 'My Custom', 'Custom_Field__c' => 'Some Value' });

Example: Using in a Test Class

@isTest private class MyServiceTest { @isTest static void testMyService() { Account testAcc = (Account) TestDataFactory.createRecord('Account', new Map<String, Object>{ 'Name' => 'Acme Corp' }); Contact testCon = (Contact) TestDataFactory.createRecord('Contact', new Map<String, Object>{ 'LastName' => 'Doe', 'AccountId' => testAcc.Id }); // Your test logic here System.assertNotEquals(null, testAcc.Id); System.assertEquals(testAcc.Id, testCon.AccountId); } }

Tips & Gotchas

  • Required Fields:
    Make sure you provide all required fields for the object, or the insert will fail.

  • Type Casting:
    Cast the returned SObject to the correct type (e.g., (Account)) when you need to access fields.

  • Bulk Testing:
    Use the createRecords method for testing triggers, batches, or any logic that processes many records at once.

  • Custom Logic:
    For more complex objects, you can still build specialized helper methods in your factory if needed.


Conclusion

A dynamic Test Data Factory pattern like this can save you hours of copy-paste and make your test suite much easier to maintain. Whether you’re working with Accounts, Contacts, Opportunities—or custom objects—you only need one utility class for all your test data creation needs.

Do you use a test data factory in your org? Have questions or tips? Share in the comments!

0 Comments

Post a Comment

Post a Comment (0)

Previous Post Next Post