-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6644da7
commit 02d0a3f
Showing
6 changed files
with
381 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,195 @@ | ||
# Salesforce DX Project: Next Steps | ||
<div align="center"> | ||
<h1>Salesforce Test Data Framework</h1> | ||
<p> | ||
Easy and intuitive way to create Salesforce unit test data. | ||
</p> | ||
|
||
Now that you’ve created a Salesforce DX project, what’s next? Here are some documentation resources to get you started. | ||
<h4> | ||
<a href="https://cesarparra.github.io/test-data-framework">Documentation</a> | ||
<span> · </span> | ||
<a href="https://github.com/cesarParra/test-data-framework/issues">Report Bug</a> | ||
<span> · </span> | ||
<a href="https://github.com/cesarParra/test-data-framework/issues">Request Feature</a> | ||
</h4> | ||
</div> | ||
|
||
## How Do You Plan to Deploy Your Changes? | ||
<br /> | ||
|
||
Do you want to deploy a set of changes, or create a self-contained application? Choose a [development model](https://developer.salesforce.com/tools/vscode/en/user-guide/development-models). | ||
## About | ||
|
||
## Configure Your Salesforce DX Project | ||
The Salesforce Test Data Framework allows you to intuitively create Salesforce SObject data for your | ||
unit tests using a fluent language. | ||
|
||
The `sfdx-project.json` file contains useful configuration information for your project. See [Salesforce DX Project Configuration](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_ws_config.htm) in the _Salesforce DX Developer Guide_ for details about this file. | ||
It provides base support for any SObject, standard or custom, out of the box, but allows you | ||
to adapt it to your custom needs. | ||
|
||
## Read All About It | ||
## Getting Started | ||
|
||
- [Salesforce Extensions Documentation](https://developer.salesforce.com/tools/vscode/) | ||
- [Salesforce CLI Setup Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm) | ||
- [Salesforce DX Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_intro.htm) | ||
- [Salesforce CLI Command Reference](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference.htm) | ||
### Deploying | ||
|
||
To deploy clone or download this repo and push the contents of `force-app/main/default/classes` to your org. | ||
|
||
### Usage | ||
|
||
#### Quick Start | ||
|
||
Tip: See the `IntegrationTests` class for more in depth example usages of the framework. | ||
|
||
A record can be created through the following syntax | ||
|
||
```apex | ||
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 | ||
|
||
```apex | ||
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: | ||
|
||
```apex | ||
Contact anyContact = (Contact)SObjectTestDataBuilder.of(Contact.SObjectType) | ||
.with(Contact.FirstName, 'John') | ||
.with(Contact.LastName, 'Smith') | ||
.registerNewForInsert(); | ||
SObjectTestDataBuilder.commitRecords(); | ||
``` | ||
|
||
#### Relationships | ||
|
||
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: | ||
|
||
```apex | ||
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: | ||
|
||
```apex | ||
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: | ||
|
||
```apex | ||
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`. | ||
|
||
#### Custom Builders | ||
|
||
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): | ||
|
||
```apex | ||
@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); | ||
} | ||
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 extending `SObjectTestDataBuilder`. | ||
* You can optionally override the `beforeInsert` and `afterInsert` methods, which will allow you to execute code before and after hitting the database for the insertion of your records. | ||
|
||
#### Default resolution vs. Explicitly specifying | ||
|
||
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: | ||
|
||
```apex | ||
SObjectTestDataBuilder.registerBuilder(Order.SObjectType, CustomOrderBuilder.class); | ||
``` | ||
|
||
|
||
### Tests | ||
|
||
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. | ||
|
||
## Roadmap | ||
|
||
* [ ] Release as unmanaged package | ||
* [ ] In-memory support | ||
|
||
|
||
## Contributing | ||
|
||
Contributions are always welcome! | ||
|
||
|
||
## License | ||
|
||
Distributed under the MIT License. See LICENSE file for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# ITestDataCallback | ||
|
||
Callback with methods to be executed before and after database calls. Do not implement directly, rather extend [SObjectTestDataBuilder](/Misc/SObjectTestDataBuilder.md) and override the methods provided there. | ||
|
||
## Methods | ||
### `beforeInsert(SObject record)` | ||
|
||
Executes before inserting the SObject for this builder into the database. | ||
|
||
#### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|`record`|The SObject that will be inserted into the database.| | ||
|
||
### `afterInsert(SObject record)` | ||
|
||
Executes after inserting the SObject for this builder into the database. | ||
|
||
#### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|`record`|The SObject that will be inserted into the database.| | ||
|
||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# TestDataUnitOfWork | ||
|
||
`ISTEST` | ||
|
||
Provides an implementation of the Enterprise Application Architecture Unit Of Work, as defined by Martin Fowler http://martinfowler.com/eaaCatalog/unitOfWork.html "When you're pulling data in and out of a database, it's important to keep track of what you've changed; otherwise, that data won't be written back into the database. Similarly you have to insert new objects you create and remove any objects you delete." "You can change the database with each change to your object model, but this can lead to lots of very small database calls, which ends up being very slow. Furthermore it requires you to have a transaction open for the whole interaction, which is impractical if you have a business transaction that spans multiple requests. The situation is even worse if you need to keep track of the objects you've read so you can avoid inconsistent reads." "A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work." In an Apex context this pattern provides the following specific benefits - Applies bulkfication to DML operations, insert, update and delete - Manages a business transaction around the work and ensures a rollback occurs (even when exceptions are later handled by the caller) - Honours dependency rules between records and updates dependent relationships automatically during the commit | ||
|
||
## Constructors | ||
### `TestDataUnitOfWork(List<Schema.SObjectType> sObjectTypes)` | ||
|
||
Constructs a new UnitOfWork to support work against the given object list | ||
|
||
#### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|`sObjectTypes`|A list of objects given in dependency order (least dependent first)| | ||
|
||
### `TestDataUnitOfWork(List<Schema.SObjectType> sObjectTypes, UnresolvedRelationshipBehavior unresolvedRelationshipBehavior)` | ||
|
||
Constructs a new UnitOfWork to support work against the given object list | ||
|
||
#### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|`sObjectTypes`|A list of objects given in dependency order (least dependent first)| | ||
|`unresolvedRelationshipBehavior`|If AttemptOutOfOrderRelationships and a relationship is registered where a parent is inserted after a child then will update the child post-insert to set the relationship. If IgnoreOutOfOrder then relationship will not be set.| | ||
|
||
### `TestDataUnitOfWork(List<Schema.SObjectType> sObjectTypes, IDML dml)` | ||
|
||
Constructs a new UnitOfWork to support work against the given object list | ||
|
||
#### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|`sObjectTypes`|A list of objects given in dependency order (least dependent first)| | ||
|`dml`|Modify the database via this class| | ||
|
||
### `TestDataUnitOfWork(List<Schema.SObjectType> sObjectTypes, IDML dml, UnresolvedRelationshipBehavior unresolvedRelationshipBehavior)` | ||
|
||
Constructs a new UnitOfWork to support work against the given object list | ||
|
||
#### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|`sObjectTypes`|A list of objects given in dependency order (least dependent first)| | ||
|`dml`|Modify the database via this class| | ||
|`unresolvedRelationshipBehavior`|If AttemptOutOfOrderRelationships and a relationship is registered where a parent is inserted after a child then will update the child post-insert to set the relationship. If IgnoreOutOfOrder then relationship will not be set.| | ||
|
||
--- | ||
## Methods | ||
### `onCommitWorkStarting()` | ||
### `onCommitWorkFinishing()` | ||
### `registerNew(SObject record, ITestDataCallback.ITestDataCallback callback)` | ||
|
||
Register a newly created SObject instance to be inserted when commitWork is called | ||
|
||
#### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|`record`|A newly created SObject instance to be inserted during commitWork| | ||
|`callback`|TestDataCallback.ITestDataCallback to be called before and after record insert.| | ||
|
||
### `registerRelationship(SObject record, Schema.SObjectField relatedToField, SObject relatedTo)` | ||
|
||
Register a relationship between two records that have yet to be inserted to the database. This information will be used during the commitWork phase to make the references only when related records have been inserted to the database. | ||
|
||
#### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|`record`|An existing or newly created record| | ||
|`relatedToField`|A SObjectField reference to the lookup field that relates the two records together| | ||
|`relatedTo`|A SObject instance (yet to be committed to the database)| | ||
|
||
### `commitWork()` | ||
|
||
Takes all the work that has been registered with the UnitOfWork and commits it to the database | ||
|
||
--- | ||
## Enums | ||
### UnresolvedRelationshipBehavior | ||
|
||
Unit of work has two ways of resolving registered relationships that require an update to resolve (e.g. parent and child are same sobject type, or the parent is inserted after the child): AttemptResolveOutOfOrder - Update child to set the relationship (e.g. insert parent, insert child, update child) IgnoreOutOfOrder (default behaviour) - Do not set the relationship (e.g. leave lookup null) | ||
|
||
|
||
--- | ||
## Classes | ||
### SimpleDML | ||
#### Methods | ||
##### `dmlInsert(List<SObject> objList)` | ||
###### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|
||
##### `dmlUpdate(List<SObject> objList)` | ||
###### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|
||
--- | ||
|
||
### UnitOfWorkException | ||
|
||
UnitOfWork Exception | ||
|
||
|
||
--- | ||
## Interfaces | ||
### IDML | ||
#### Methods | ||
##### `dmlInsert(List<SObject> objList)` | ||
###### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|
||
##### `dmlUpdate(List<SObject> objList)` | ||
###### Parameters | ||
|Param|Description| | ||
|---|---| | ||
|
||
--- | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.