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.
- 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.
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;
}
}
Dynamic Object Creation:
UsesSchema.getGlobalDescribe()
to get the SObject token for any object (standard or custom) by API name.Flexible Field Assignment:
Pass aMap<String, Object>
of field API names to values. The method loops through the map and sets each field.Bulk Support:
ThecreateRecords
method lets you insert lists of records in one go—great for testing batch logic.
Account acc = (Account) TestDataFactory.createRecord('Account', new Map<String, Object>{
'Name' => 'Test Account'
});
Contact con = (Contact) TestDataFactory.createRecord('Contact', new Map<String, Object>{
'LastName' => 'Smith',
'Email' => 'smith@example.com'
});
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);
SObject customObj = TestDataFactory.createRecord('Custom_Object__c', new Map<String, Object>{
'Name' => 'My Custom',
'Custom_Field__c' => 'Some Value'
});
@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);
}
}
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 thecreateRecords
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.
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!
Post a Comment