Test Data Factory
Seamless creation of SObject records for unit tests.
Features:
- Built-in and Customizable Factory: Provides an out-of-the-box SObject factory, with an extensible base to define custom record templates.
- Automatic Population of Required Fields: Ensures mandatory fields are pre-populated.
- Support for Read-Only Fields: Includes options to set values for read-only fields, including system fields, formula fields, and child records.
- Managed Package Compatibility: Fully supports managed package records.
Example
// Use custom AccountFactory implementation with default account template.
Account account = (Account) new AccountFactory()
// Make 1 mocked account with ID without DML.
.mocked()
// Override LastModifiedDate system value.
.setReadOnly(Schema.Account.LastModifiedDate, mockedDatetime)
// Override Name via test-specific target account record.
.build(new Account(Name = 'Target Name'))
.toSObject();
Assert.isNotNull(account.Id, 'Account ID was not mocked.');
// Use out-of-the-box generic contact factory.
List<Contact> contacts = (List<Contact>) new sobj.BaseSObjectFactory()
// Create 5 contacts.
.created(5)
// Set Account relationship via test-specific target contact record.
.build(new Contact(
AccountId = anotherAcc,
Description = 'Contact with mocked account example.'
))
.toList();
Assert.areEqual(5, contacts.size(), 'Expected number of contacts not created.');
Installation
You can either install our free Managed Package or deploy code unpackaged.
Version ID: 04tJ80000000RPeIAM
Managed Package
Install Managed Package using this URL:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04tJ80000000RPeIAM
or using sf cli:
sf package install -p 04tJ80000000RPeIAM -o my-org
Unpackaged
Use our sfdx plugin to install all components in the src/sobj/core/
and src/sobj/example/
without cloning:
sf kratapps remote deploy start \
--repo-owner kratapps \
--repo-name test-data-factory \
--source-dir src/sobj/core/ \
--source-dir src/sobj/example/ \
-o my-org
or clone the project and deploy using standard sf command:
git clone https://github.com/kratapps/test-data-factory.git
cd test-data-factory
sf project deploy start \
--source-dir src/sobj/core/ \
--source-dir src/sobj/example/ \
-o my-org
Usage
You can create records using the TDF immediately in small projects or sObjects that are created only occasionally in unit tests. For other cases, we recommend extending SObjectFactory
and/or SObjectFactoryScenario
for each sObject type. SObjectFactory/SObjectFactoryScenario
gives you more flexibility.
Operations
Choose one of these operations: create, mock and insert.
Prefer crete or mock over insert to improve performance by avoiding DML.
Usage Recommendation: Select one of the following operations for making test data: create
, mock
, or insert
.
- create: Generates
sObject
records without DML. - mock: Mocks
sObject
records, avoiding DML and providing mock IDs. - insert: Executes DML to insert records.
Favor
create
ormock
overinsert
to improve performance by minimizing DML operations.
create | mock | insert | |
---|---|---|---|
Performance | fast | fast | slower |
With IDs | ✕ | ✓ | ✓ |
Queryable | ✕ | ✕ | ✓ |
Custom Settings | ✓ | ✓ | ✓ |
Custom Metadata | ✓ | ✓ | ✕ |
Custom Template SObject Factory
You can either use generic sobj.BaseSObjectFactory implementation to make your records or ideally implement a SObject factory template for your SObjects.
Main benefit on implementing templates is to provide a blueprint of your records to all apex tests. These blueprints/templates are called defaults
. Each apex test can then override defaults
using a target
record, provided via build
method.
Template Example Implementation
Template SObject factory class extends sobj.BaseSObjectFactory
. Then in your apex test use your template factory instead of base SObject factory.
- Usage of custom template factory:
new ContactFactory().created()
- Usage of base factory:
new sobj.BaseSObjectFactory().created()
@IsTest
public without sharing class ContactFactory extends sobj.BaseSObjectFactory {
// Create template defaults, common for all tests using ContactFactory.
public SObject createDefaults(SObject target) {
return new Contact(FirstName = 'Jon', LastName = 'Doe', Email = 'jdoe@acme.com');
}
// Prepare required parent records.
public override SObject makeParent(SObjectField sObjectField, SObject target) {
if (sObjectField == Contact.AccountId) {
return new AccountFactory().inserted().build(new Account()).toSObject();
}
return null;
}
// We can include test method to verify a record is insertable without errors.
@IsTest
static void testInsert() {
Contact contact = (Contact) new ContactFactory().inserted().build(new Contact()).toSObject();
Assert.isNotNull(contact.Id, 'Record not inserted.');
}
}
Template Methods
-
createDefaults
This method creates a new record with default values. Avoid any DML Operation here as it is called for every sObject created. -
makeParent (optional)
This method is called only once for each relationship.
The parent should be created in this method, because the method is not called for records that already have the parent. This way you can reduce redundant DML statements. -
getDmlOptions (optional)
Every factory comes with DML Options, default DML Options haveduplicateRuleHeader.allowSave
set to true.
You can override this behavior. -
requireRecordType (optional)
If sObject has record types, it is enforced to set the RecordTypeId in unit tests.
You can disable this feature by overriding this method. -
autoPopulateRequiredFields (optional)
Auto-population of required fields. This feature is not deterministic and can have impact on performance. Disabled by default.
Features
Created vs Mocked vs Inserted
Create
Account account = (Account) new sobj.BaseSObjectFactory
.created()
.build(new Account())
.toSObject();
Assert.isNull(account.Id, 'Record should not have ID.');
Mock
Account account = (Account) new sobj.BaseSObjectFactory
.mocked()
.build(new Account())
.toSObject();
Assert.isNotNull(account.Id, 'Record should have mock ID.');
Insert
Account account = (Account) new sobj.BaseSObjectFactory
.inserted()
.build(new Account())
.toSObject();
Assert.isNotNull(account.Id, 'Record should have real ID.');
Build Single vs Multiple Records
Create one record and cast to SObject.
Account account = (Account) new sobj.BaseSObjectFactory
.created()
.build(new Account())
.toSObject();
Create one record and cast to a list.
List<Account> accounts = (List<Account>) new sobj.BaseSObjectFactory
.created()
.build(new Account())
.toList();
Assert.areEqual(1, accounts.size(), 'Different number of records created.');
Create 200 records and cast to a list.
List<Account> accounts = (List<Account>) new sobj.BaseSObjectFactory
.created(200)
.build(new Account())
.toList();
Assert.areEqual(200, accounts.size(), 'Different number of records created.');
Set Required Fields
Account account = (Account) new sobj.BaseSObjectFactory
.created()
.setRequiredFields()
.build(new Account())
.toList();
Assert.isNotNull(account.RequiredField__c, 'Required field not populated.');
Set Read Only Fields
Datetime mockedDatetime = Datetime.now();
Account account = (Account) new sobj.BaseSObjectFactory
.created()
.setReadOnly(Schema.Account.LastModifiedDate, mockedDatetime)
.build(new Account())
.toList();
Assert.areEqual(mockedDatetime, account.LastModifiedDate, 'Read only field not populated.');
Set Children
Simple use case for injecting related list records. Useful for mocking nested queries.
List<Account> children = (List<Account>) new sobj.BaseSObjectFactory()
.created(5)
.build(new Account(Name = 'child'))
.toList();
Account parent = (Account) new sobj.BaseSObjectFactory()
.created()
.setChildren(Account.ParentId, children)
.build(new Account(Name = 'parent'))
.toSObject();
Assert.areEqual(5, parent.ChildAccounts.size(), 'Expected 5 child accounts populated.');
Utils
Get mocked/inserted record by ID.
Id recordId = sobj.MockId.getMockId(Account.SObjectType);
Generate and set mock ID to a list of records.
sobj.MockId.setMockIds(accountList);
Best Practices
Common best practices while using this TDF.
Insertable records
You should be able to insert every record without providing any values in the call. The following snippet should work in every unit test for all SObjects:
Contact contact = (Contact) new ContactFactory().inserted().build(new Contact()).toSObject();
Assert.isNotNull(contact.Id, 'Record not inserted.');
Disable auto populate required fields
For SObjects with numerous fields, disable auto-population of required fields to enhance performance.