Easy and intuitive way to create Salesforce unit test data.
The Salesforce Test Data Builder framework allows you to intuitively create Salesforce SObject data for your unit tests using a fluent language.
It provides base support for any SObject, standard or custom, out of the box, but allows you to adapt it to your custom needs.
To deploy clone or download this repo and push the contents of force-app/main/default/classes
to your org.
Tip: See the IntegrationTests
class for more in depth example usages of the framework.
A record can be created through the following syntax
Contact anyContact = (Contact)SObjectTestDataBuilder.of(Contact.SObjectType).registerNewForInsert();
SObjectTestDataBuilder.commitRecords();
SObjectTestDataBuilder.of
receives an SObjectType
for the type of record that you want to create. Calling
registerNewForInsert
on the returned object will queue up the record for bulk insertion once
SObjectTestDataBuilder.commitRecords()
gets called.
You can also create multiple records by using the registerNewForInsert
overload that takes the number
of records to create
List<Contact> multipleContacts = (List<Contact>)SObjectTestDataBuilder.of(Contact.SObjectType).registerNewForInsert(5);
SObjectTestDataBuilder.commitRecords();
By default, the framework will try to resolve for any required fields on the SObject. In the Contact
example,
the LastName
field is a required text field, so the framework will use a String
from the DefaultTestData
class to fill up that field.
Feel free to modify that class to change the defaults for any of the different field types.
During record creation you can also explicitly specify any field data you want to create by chaining with
calls
and passing an SObjectField and a value:
Contact anyContact = (Contact)SObjectTestDataBuilder.of(Contact.SObjectType)
.with(Contact.FirstName, 'John')
.with(Contact.LastName, 'Smith')
.registerNewForInsert();
SObjectTestDataBuilder.commitRecords();
When creating test data you might want to also create relationship records, either parent-to-child or child-to-parent, which are both supported by the framework.
To create a parent-to-child relationship you can use the withChild
method:
Account accountWithChild = (Account)SObjectTestDataBuilder.of(Account.SObjectType)
.withChild(
SObjectTestDataBuilder.of(Contact.SObjectType),
Contact.AccountId
)
.registerNewForInsert();
SObjectTestDataBuilder.commitRecords();
The withChild
method takes 2 arguments: a builder for the child (which can be obtained by calling SObjectTestDataBuilder.of
)
and the relationship field between the 2 objects.
Retrieving child records
Once the records have been inserted to the database (after the call to SObjectTestDataBuilder.commitRecords()
) you do not
need to query the database to get the reference to the children. Instead, the framework keeps a reference of all inserted records
so you can retrieve the children by using the SObjectTestDataBuilder.getChildrenOfTypeById
method:
List<Contact> accountChildren = SObjectTestDataBuilder.getChildrenOfTypeById(accountWithChild.Id, Contact.SObjectType);
To create child-to-parent relationships the framework has the concept of "late binding", which is a way to specify a relationship between objects without calling the database until necessary for bulkification purposes.
The late binding is used with the regular with
method that is used to specify any field value. But instead
of passing a record ID value (which is valid as well, but will not take advantage of bulkification) you pass
in a SObjectTestDataBuilder.LateBinding
object, which can be obtained by calling the SObjectTestDataBuilder.lateBinding
method:
SObjectTestDataBuilder.of(OrderItem__c.SObjectType)
.with(OrderItem__c.Order__c, SObjectTestDataBuilder.lateBinding(Order__c.SObjectType))
.registerNewForInsert();
SObjectTestDataBuilder.commitRecords();
This will create both the OrderItem__c
and the parent Order__c
.
When creating SObjects you will probably want more control over the data than what the default builder gives you. The framework also allows you to create custom builders for different SObjectTypes.
To create a custom builder you will need to both extend SObjectTestDataBuilder
and implement ITestDataBuilder
.
For the framework to automatically detect your custom builder it also *needs to have a name that ends in TestDataBuilder
(though there is a way to explicitly specify the builder to the framework, explained below).
Any custom builder should start with some boilerplate code that looks as follows:
@IsTest
public with sharing class OrderTestDataBuilder extends SObjectTestDataBuilder implements ITestDataBuilder {
public override SObjectType getSObjectType() {
return Order__c.SObjectType;
}
public OrderTestDataBuilder with(SObjectField field, Object value) {
return (OrderTestDataBuilder) withData(field, value);
}
public Order__c registerNewForInsert() {
return (Order__c) this.registerSObjectForInsert();
}
public List<SObject> registerNewForInsert(Integer numberOfRecords) {
return this.registerSObjectsForInsert(numberOfRecords);
}
public OrderTestDataBuilder withChild(ITestDataBuilder childBuilder, SObjectField relationshipField) {
return (OrderTestDataBuilder)this.withChildData(childBuilder, relationshipField);
}
protected override Map<SObjectField, Object> getDefaultValueMap() {
return new Map<SObjectField, Object> {
Order__c.Customer__c => bindTo(SObjectTestDataBuilder.of(Account.SObjectType))
};
}
}
Creating a custom builder gives you the following functionality:
- Allows you to create additional
withSomeField
method, which will make your code more fluent - You can optionally override the
getDefaultValueMap
method, which will allow you to create default data tailored to the SObject.- Note by the example that you can use late binding by calling the
bindTo
method automatically provided to you when extendingSObjectTestDataBuilder
.
- Note by the example that you can use late binding by calling the
- You can optionally override the
beforeInsert
andafterInsert
methods, which will allow you to execute code before and after hitting the database for the insertion of your records.
By default, the framework will look for any class that ends with TestDataBuilder
to try and automatically resolve with test data builder to use.
When one is not found then the DefaultTestDataBuilder
will be used, which will attempt to resolve required fields with default data. But you can also
explicitly specify which builder to use to the framework in the unit test itself, in case you want to either override the automatic resolution and use a different builder,
or if you simply don't want to follow the naming convention.
You can do that by using the SObjectTestDataBuilder.registerBuilder
method:
SObjectTestDataBuilder.registerBuilder(Order.SObjectType, CustomOrderBuilder.class);
This repo provides the IntegrationTests
class with examples of all functionality provided by the framework. The
kitchenSinkExample
provides an example of a complex hierarchy of objects.
- Release as unmanaged package
- In-memory support
Contributions are always welcome!
Distributed under the MIT License. See LICENSE file for more information.