The Builder Pattern is a software design pattern intended to add more abstraction to the instantiation of Objects, where you can preset certain steps of the process simply by, setting some properties with default values coherent with the context of the execution.
Say you have a Custom Object with several required fields, but you need to test a method that only needs a few of them, wouldn’t it be easier to create it by calling something like:
Custom_Object__c example = new TestDataFactory.CustomObjectBuilder.build();
Instead of:
1 2 3 4 5 6 7 8 9 10 |
Custom_Object__c example = new Custom_Object__c( Attribute1 = ‘Property’, Attribute2 = ‘Property’, Attribute3 = ‘Property’, Attribute4 = ‘Property’, Attribute5 = ‘Property’, Attribute6 = ‘Property’, Attribute7 = ‘Property’, ... ); |
So, first of all, what is TestDataFactory? And where does it come from?
Essentially, TestDataFactory, is the Builder (remember? The one in the title). It contains the methods that will encapsulate the constructors of all the classes we’ll need during our tests.
At this point we have a couple of clues of how this upper class should look like: It’s a class (duh) and it’s going to be used for our tests:
1 2 3 |
@isTest public class TestDataFactory { } |
Well, that’s a start.
The @isTest annotation implies that the class won’t count to the org’s code limit, and will also be executed exclusively by Test Methods.
Now we need the class to do something. This is the fun part.
Inside the class, we’ll need subclasses to handle each individual object we want it to instantiate. Those classes should have the necessary settings to cope with their object’s demands and also be easy to handle at the time to create a Test Method.
Let’s work with a Person__c:
1 2 3 4 5 6 7 8 9 |
public class PersonBuilder { private String name = ‘John’; private String lastname = ‘Doe’; private String dateOfBirth; private String school; private String address; public eventBuilder() {} ... |
Here’s the basic structure of the Builder, a class with several private attributes (all Strings, how convenient) and a constructor method.
The idea is to reflect all the sObject’s properties we need as private properties and set the ones required with dummy data just to not do it later.
Looks like a wrapper? It’s kinda like a wrapper.
We’re only two steps away from getting this working:
-
Customize (set) the values for all the attributes, and
-
Create the actual object
To set the attributes we’ll use (drumroll…) a setter method with a little twist: the return of the object (this, for family and friends)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... public eventBuilder() {} public EventBuilder setName(String value) { this.name = value; return this; } public EventBuilder setLastname(String value) { this.lastname = value; return this; } ... |
One of those for each attribute.
Next, the instance, the build() method:
1 2 3 4 5 6 7 8 9 |
public Person__c build() { Person__c person = new Person__c(); person.Name = this.name; person.LastName = this.lastname; ... return person; } |
As we can see, the Name and LastName fields are set with the classes’ private attributes, and will keep their default values if the setting methods are not called, and finally returning an instance of Person__c
To wrap it up:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@isTest public class TestDataFactory { public class PersonBuilder { private String name = ‘John’; private String lastname = ‘Doe’; public eventBuilder() {} public EventBuilder setName(String value) { this.name = value; return this; } public EventBuilder setLastname(String value) { this.lastname = value; return this; } public Person__c build() { Person__c person = new Person__c(); person.Name = this.name; person.LastName = this.lastname; return person; } } } |
And finally, the call from a Test (ideally inside a @testSetup method):
1 2 3 4 |
Person__c person = new TestDataFactory.PersonBuilder(). setName(‘Sample’). setSchool(‘Some School’). build(); |
And that’s it, we now have a Person object named Sample Doe, who studied in Some School.
This creates objects in the same way and with the same default data every time, helping with the testing process and resulting in much clearer code.
References: