This week’s guest post is a cool technical post about Apex Tests by Nikos Mitrakis.
Apex Tests are perceived as the hated necessity by most Salesforce developers: they are really important to keep high code quality levels but they are not the most fun part of coding with Salesforce.
I casually discovered Nikos through his GitHub page: I had a read at this Forceea Apex Test Framework, and I asked him to write a post to describe this cool piece of Salesforce tech.
Nikos has started his Salesforce journey in 2014. He started with Salesforce as a hobby (which still remains) and his wife continously threatens him with divorce if he doesn’t stop playing with his various Orgs all the time.
He has a Physics degree, a Microsoft SQL Server BI certification and a few Salesforce certifications. He likes writing code and discoverig how huge the Salesforce ecosystem is. He adores talking about his personal projects, he likes good sci-fi films, he’s been working with a Mac for more than a year, and he started the Salesforce Developer Group in Greece in 2016 (admittedly without much success).
Nikos is working as a Salesforce Developer at the Johnson & Johnson EMEA Development Center in Ireland. If he wasn’t a Salesforce Developer, he’d like to be a Developer in Salesforce.
What is a Data Factory?
Generally speaking, a Data Factory is a set of tools that generate and insert data. It’s very important when you develop a test method in an Apex Test Class, since you usually have to insert records to complete the testing. Using a Data Factory you make your life easier and your work more productive!
A Data Factory is also required when you want to test a report, a trigger/class or another process under Large Data Volumes (e.g. how fast is our report when there are 1 million records?), for User Acceptance testing (UAT), and for training â when you donât want to create a Full Sandbox or when you want to create data which donât exist in the production system yet.
What is Forceea and how I can install it?
Forceea (forËsÄa) is a data factory framework for Salesforce and an open source project (https://github.com/nmitrakis/Forceea) in GitHub. Itâs a framework, because it gives the programmatic tools for a developer (or administrator, why not?) to easily generate, insert and delete SObject records.
âYet another data factory?â you may say. Well, not at all!
But first letâs see how you install it.
When you download the files, your installation depends on your preference: classic metadata or DX? For the first option, you may want use the Ant (the build files are included) or the CLI, for example with
sfdx force:mdapi:deploy -u <OrgAlias> -d <folder> -w 5).
For DX, I suggest the CLI, using the command
sfdx force:source:push -u <OrgAlias>
How can I generate the data I want?
OK, you have installed the framework. Now what?
I think the better way to understand how it works is by example: suppose you want to insert 100 Opportunity records and in your Org the Opportunity object has the additional required custom field MyField__c (which is a lookup to the custom object MyObject__c). Of course the Opportunity has its own required fields (like Stage).
The first step is to create an FObject:
FObject myObj = new FObject(Opportunity, 100);
Next, you declare the field definitions. A field definition uses a descriptive âlanguageâ to define what kind of data you want to create for this particular field. This language is called SDDL (Sample Data Definition Language). Donât be afraid! Itâs not a new programming language and itâs very easy to learn.
Letâs start with the first field you want to define, which is the Record Type (API name: RecordTypeId). Do you want your records to have a specific record type (letâs say BigDeal)? Then give this field definition:
myObj.setDefinition('RecordTypeId', 'static value(BigDeal)');
static here is the command and value is the parameter. The static command creates (what else?) static values, that is values which are the same in every record you create.
Do you want a random record type? Then you need the following definition:
obj.setDefinition('RecordTypeId', 'random type(picklist)');
The random command does exactly what you understood, itâs a really powerful command and you can even decide that every time you get the same (but random) data!
But wait! I hear you saying that the Record Type field is not a picklist field, and of course youâre right (itâs a Lookup)! Forceea handles this âspecialâ field as a picklist field, making things easier for you. Say âThank you 4caâ..
So, we move to our next field, which is the Close Date (API name CloseDate). This has a Date type, and letâs suppose you want it to have random Date values from this year:
myObj.setDefinition('CloseDate', 'random type(date) from(2018-01-01) to(2018-12-31)');
The type parameter is the boss here â it defines what kind of data you get. Give type(Boolean) and you get random True/False values. Give type(phone) and you have random phone numbers, etc
The Amount is a currency, so you decide to take random values from 100,000 to 1,000,000 and round these values to the nearest thousand. Difficult? No, itâs very easy!
myObj.setDefinition('Amount', 'random type(number) from(100000) to(1000000) scale(-3)');
Every opportunity that respectâs itself should have an Account, so you decide that the AccountId field will take an ID from any random Account, but youâll get accounts without two specific Industry values. You asked for it, you get it:
myObj.setDefinition('AccountId', 'random lookup(Account) field(Industry) except(Banking,Chemicals) source(forceea)
I think the above definition needs some clarifications. The lookup(Account) instructs the framework to fetch records from the Account object and the field(Industry) except(âŚ) to get only records for which the Industry field has any value except âBankingâ and âChemicalsâ.
But what is source(forceea)? This parameter defines the âsourceâ of the lookup objectâs records:
- If itâs source(salesforce), the framework will query the Salesforce database and get the lookup records.
- If itâs source(forceea) and the lookup object has been previously created (in the same context/transaction), it gets the records from there (which of course is much faster).
- If itâs source(forceea) and the lookup object hasnât been created (or the records have been inserted and deleted), it inserts some records of this lookup object.
Now that you have the opportunity account, donât forget the opportunity Name (API name Name). Letâ say you want it to have the format <AccountName> – Opportunity for <Random text (with words) from 15 to 30 chars>
So, what is our definition? Well, we donât have one, but 3 definitions to deliver this:
myObj.setDefinition('Name', 'copy field(AccountId) from(Account.Name)');
This definition gets the Name of the related account.
myObj.setDefinition('Name', 'static value(" â Opportunity for ")');
As you see, this is just a simple text value.
myObj.setDefinition('Name', 'random type(text) minlength(15) maxlength(30)');
And finally, we get our random text with âLatinoidâ random words, like âPartem inermis ius impedit eamâ
Keep in mind that with the random type(text) definition, the first character of the text is always a capital and the same word is never repeated twice in a row.
Your next field is Lead Source (which is a picklist field with the API name LeadSource), and here you want to have any declared picklist value except from âPartnerâ and âOtherâ:
myObj.setDefinition('LeadSource', 'random type(picklist) except(Partner, Other)');
If you wanted these two values only, you could give the definition:
myObj.setDefinition('LeadSource', 'random type(list) value(Partner, Other)');
or if you needed just a specific picklist value:
myObj.setDefinition('LeadSource', 'static value(Partner)');
Now youâve finished with your field definitions, but remember that we havenât defined all required fields (like Stage or myField__c). Of course you donât have to worry about that, because the framework automatically identifies and defines all required fields. There are specific rules for setting the suitable field definition, but (without going to many details) we could briefly say that the field definition for a required field depends on the field data type, the SObject and the field name. So, after defining your fields, Forceea will define all required fields you didnât define, like Stage or myField__c.
If you need to make some changes in the created records (for example to update a field with some complex calculations), you can get the opportunities with:
List<Opportunity> myRecords = (List<Opportunity>) myObj.getRecords();
Then do your changes, e.g.
for (Opportunity objRecord : myRecords) { objRecord.Amount = ⌠// your changes here }
and just insert the amended records with
myObj.insertRecords(true);
Forceea has many other field definitions, to create random real first and last names (with male and female names), real postal addresses (address, city, postal code, state, country), URLs, email addresses, phone numbers, Boolean values and Datetime values. And donât forget that it supports BLOBs (e.g. to create email attachments).
It also provides definitions with the serial command (e.g. serial type(number) from(1) step(1.5) scale(2)) to create serial integer, decimal, currency, percentage, date and datetime values.
As you understand, there are many methods to
- get or delete field definitions
- create records from a specific serial number (very useful when you insert records with the serial command in different transactions)
- insert records in groups (a valuable tool for your test methods)
- define the Verbose mode (how many debug logs you get)
- get the errors (yes, there is a detailed error system)
The framework handles field dependencies (dependent picklists) and it protects you from doing errors like defining the wrong definition for a specific field data type or defining multiple definitions for a field (e.g. Date) which can have only one.
How can I see the frameworkâs messages?
Forceea doesnât have a UI (for the moment). The framework uses Debug Log to post its messages, which show
- The errors during the process
- The process milestones completed
- The data of the first created records
Here is a sample output, just to get an idea of what this looks like (please note that the field definitions in this sample are not the field definitions you used previously).Keep in mind that you have many options to reduce the quantity of logs, for example to get almost nothing on a production system (which by the way is accomplished using a Custom Metadata type).
Whatâs next?
When you use Forceea in a test method, you really donât need to create many records, so the 10,000ms CPU limit isnât an actual problem. Of course you can use the framework to insert records in a sandbox or developer Org when you execute an Apex script in the Anonymous Windows, which of course has the same 10,000ms limit. But when you want to populate a sandbox with 1,000,000 of Accounts, this is another story..
This story is a major part of the next upcoming release (v1.3). The functionality is called Async, it can insert or delete many objects in one transaction, and when you insert objects or delete 1 object itâs surprisingly fast! I have inserted 500,000 of a custom object (without any validation rules, triggers, processes, etc) in 40s! Of course its speed mainly depends on the triggers, etc you have in your actual Org, which may slow down the process significantly (thatâs life..)
Anything else?
Yes! I invite everyone to have a look at the detailed User Guide (http://bit.ly/Forceea12_UserGuide) and try the framework.
And Iâd like to say a big THANKS to Enrico for hosting this post.
Leave a Reply