Generate Data for Tests
Learning Objectives
After completing this unit, you’ll be able to:
- Describe why creating data for tests is the best practice.
- Create data specifically for unit tests.
Why Create Test Data?
A lot of developers are passionate about test data, and for good reason. Creating test data is one of the most important aspects of writing useful unit tests. Watch this video to learn more about why we create test data as part of our unit tests.
Two crucial aspects of a useful test are that it is descriptive and that every run results in the same outcome. A test that you run multiple times should always either fail or pass, unless you’ve changed the underlying code. Likewise, it should always be clear why a test failed.
For these reasons, it’s tempting to use existing data from your production org, such as accounts or contacts, to run your tests. But that’s not a great idea. The Lightning Platform prevents developers from editing Apex classes in production orgs, so to write or edit tests, you have to use a development environment. That is, you have to use either a sandbox snapshot of your production org or a Developer Edition org.
Naturally, the data in your production org changes over time and will be different from the data in your developer environment. When your tests run, either in a developer environment or in production, they have access only to the data in that org. So if you write tests using existing data, you can end up with deployment problems, because the data available in one environment isn’t always available in another environment.
That’s why it’s important for every test to create its own records. There are several methods for creating test data. In this unit we go through four of them:
- Brute force
- Test factories
- @TestSetup methods
- CSV data files
Install VS Code and Connect to Your Trailhead Playground
Brute Force
This method consists of you creating and inserting the necessary records manually on each test.
- Open VS Code.
- In the Explorer sidebar, right-click the folder
classes
, then choose SFDX: Create Apex Class. Do not choose the New File command, as that won’t create the necessary metadata .xml file.
- Enter
DataGenerationTests
as the name and accept the default directory.
- Replace the contents of the class with the following code.
@IsTest private class DataGenerationTests { @IsTest static void testBruteForceAccountCreation() { // GIVEN List<Account> accts; // WHEN Test.startTest(); accts = [SELECT Id FROM Account]; Test.stopTest(); // THEN Assert.isTrue(accts.size() > 0, 'Was expecting to find at least one account'); } }
- Click File > Save.
- Right-click the
DataGenerationTests
file you’re working on, then choose SFDX: Deploy Source To Org. If the SFDX: Deploy Source to Org option is unavailable, ensure you authorized your Trailhead Playground as an org with Salesforce CLI per the previous step.
- Click the Run Test button that appears on the
testBruteForceAccountCreation
method of the Apex Class you have just created and deployed.
- Note the test result outcome in the Output panel. Uh oh, our Apex test failed! 😯
Because this test creates no accounts, the test run fails. To fix that, let's look at what the test is doing.
A Note on Test.startTest()
Part of what makes the Salesforce platform so great are the governor limits we’ve built into it. These governor limits prevent any one user or org from using all the system resources. The limits ensure, for instance, that Apex code has only 10 seconds to execute.
Because these limits apply even to unit test execution, they can become difficult. If, for example, you need to insert and query a lot of records while setting up your test, you could hit a governor limit when you actually call the code being tested. That can lead to a false-failure, where your test fails, but not because your code is wrong.
Using the Test.startTest()
and Test.stopTest()
methods lets you tell the platform when you’re calling the code being tested. Functionally, this isolates your tested code by resetting governor limits from the startTest()
call until the stopTest()
call. For more information on governor limits, check out the link in the Resources section.
If you want your test to pass, you need to create some accounts.
- Go back to your test class in VS Code.
- Insert a new line after
List<Account> accts;
, and on this line, place the following code.Account a = new Account(name='McTesterson LLC'); insert a;
- Save your class and repeat steps 5 through 8.
This time, the test should pass.
This is effectively brute-forcing the solution. Before you call the underlying code that you’re testing, you’re creating the necessary data by creating and inserting objects. Brute force can work well, but it can lead to lengthy tests that are hard to follow. That is especially true when you start generating records that are related to other records and contain lots of field details.
Test Factories
To make your tests simpler and easier to follow, you can move your data creation into a reusable class. Called data factories, these reusable classes contain methods that model the creation of one or more objects. You don’t even have to create your own test factory! A number of open-source data factories are available online.
Let’s see what a basic test factory would look like, and how to use it.
- Open VS Code.
- In the Explorer sidebar, right-click the folder
classes
, then choose SFDX: Create Apex Class.
- Name the class
TestFactory
and accept the default directory.
- Replace the contents of the TestFactoryclass with the following code.
@isTest public class TestFactory { public static Account getAccount(String name, Boolean doInsert) { Account a = new Account(name = name); if(doInsert) { insert a; } return a; } public static Contact getContact( Id accountId, String fname, String lname, Boolean doInsert ) { Contact c = new Contact(firstName = fname, lastName = lname, accountId = accountId); if(doInsert) { insert c; } return c; } public static void generateAccountWithContacts(Integer numContacts) { Account a = getAccount('default account ltd', true); List<Contact> contacts = new List<Contact>(); for(Integer i = 0; i < numContacts; i++) { String contactName = 'contact' + i; contacts.add(getContact(a.id, contactName, contactName, false)); } insert contacts; } public static Opportunity[] generateOppsForAccount( id accountId, Decimal amount, Integer numOpps ) { List<Opportunity> opps = new List<Opportunity>(); for(Integer i = 0; i < numOpps; i++) { Opportunity o = new Opportunity(); o.name = 'Account ' + i; o.accountId = accountid; o.amount = amount; o.closeDate = Date.today().addDays(5); o.stageName = 'Prospecting'; opps.add(o); } return opps; } public static User generateUser(String profileName) { UserRole userRole = new UserRole( DeveloperName = 'TestingTeam', Name = 'Testing Team' ); insert userRole; User u = new User( ProfileId = [SELECT Id FROM Profile WHERE Name = :profileName].Id, LastName = 'last', Email = 'Cpt.Awesome@awesomesauce.com', Username = 'Cpt.Awesome.' + DateTime.now().getTime() + '@awesomesauce.com', CompanyName = 'Testing Co', Title = 'Captian', Alias = 'alias', TimeZoneSidKey = 'America/Los_Angeles', EmailEncodingKey = 'UTF-8', LanguageLocaleKey = 'en_US', LocaleSidKey = 'en_US', UserRoleId = userRole.Id ); insert u; return u; } }
- Click File > Save.
- Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
Code Highlights
This test factory isn’t very sophisticated, but you get the idea. This test factory class is built from a group of methods that generate and optionally insert data for use in testing. Notice the first line: @IsTest
? That means you can use the class and its methods only in the context of unit tests. Additionally, classes annotated with @IsTest
don’t count against your organization limits on the amount of Apex you can use.
Your class offers methods to create accounts, contacts, and, more important, an account with five contacts. That is where data factories can really start to shine—in creating networks of related objects with a single line of code.
To use your test factory, modify your DataGenerationTests
.
- Open VS Code.
- In the Explorer sidebar, click the folder
classes
.
- Select the class
DataGenerationTests
and open it.
- Place the following code snippet after your first test.
@IsTest static void testUseTestFactoryToCreateAccountsWithContacts() { // GIVEN List<Account> accts; List<Contact> contacts; TestFactory.generateAccountWithContacts(5); // WHEN Test.startTest(); accts = [SELECT Id FROM Account]; contacts = [SELECT Id FROM Contact]; Test.stopTest(); // THEN Assert.isTrue(accts.size() > 0, 'Was expecting to find at least one account'); Assert.areEqual(5, contacts.size(), 'Was expecting to find 5 contacts'); }
- Click File > Save.
- Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
- Click the Run Test button that appears on the
testUseTestFactoryToCreateAccountsWithContacts
method of the Apex Class you have just created and deployed.
Code Highlights
As in your first test, you’re checking to ensure that your test is creating test data. In this case, however, you’re using your test factory class to create the data.
TestSetup Methods
When we create tests, we want to keep them as succinct and easy to follow as possible. Separating data creation into a test factory helps, but we can go one step further.
Multiple test methods in the same class often have similar data requirements. Because of this, the Lightning Platform offers a way for you to annotate methods in your test class as @TestSetup
methods. The platform calls these methods automatically before every individual test method. Because they’re called before every test method in the class, they’re perfect for creating test data. Note, however, that the data is reset in between each and every test method's execution.
Let’s create a testSetup method.
- Go back to your
DataGenerationTests
class in VS Code.
- Add the following setup method at the top of the file, just under the line saying private.
@TestSetup static void dataCreation() { // GIVEN Account a = TestFactory.getAccount('Muddy Waters Inc.', true); Contact c = TestFactory.getContact(a.id, 'Muddy', 'Waters', true); Opportunity opp = New Opportunity(); opp.name = 'Long lost record'; opp.accountId = a.id; opp.closeDate = Date.today().addDays(14); opp.stageName = 'prospecting'; insert opp; }
- Click File > Save.
- Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
- Click the Run Test button that appears on the
testUseTestFactoryToCreateAccountsWithContacts
method, as you did in the previous step.
Uh-oh, adding this @TestSetup
method broke the second test method. Specifically, the assertion failed with the error message “Assertion Failed: Was expecting to find 5 contacts.”
The test failed because your @TestSetup
method creates a contact before the platform runs the test method. That contact, combined with the 5 contacts your test method creates, means that your test’s query finds 6, not 5, contacts. These additional records demonstrate the platform’s execution of @TestSetup
methods prior to actually executing test methods. They also demonstrate the ability of the @TestSetup
method to create records for testing.
To fix your failing test, modify the assertions to say 6, rather than 5.
- Go back to your
DataGenerationTests
class in VS Code.
- Find the line of code that reads:
Assert.areEqual(5, contacts.size(), 'Was expecting to find 5 contacts');
- Modify that line of code to specify 6 contacts, like this.
Assert.areEqual(6, contacts.size(), 'Was expecting to find 6 contacts');
- Click File > Save.
- Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
- Click the Run Test button that appears on the
testUseTestFactoryToCreateAccountsWithContacts
method, as you did in the previous step.
Code Highlights
This testSetup
method is pretty basic, but it does incorporate some of what you’ve already done; namely, use your test factory to generate data.
Lets add a new testMethod
to look for opportunities.
- Go back to your
DataGenerationTests
class in VS Code.
- Create a new test method with the following code:
@IsTest static void testAtTestSetupMethodsRule() { // WHEN List<Opportunity> opps = [SELECT Id, AccountId FROM Opportunity]; // THEN Assert.areEqual(1, opps.size(), 'Expected test to find a single Opp'); }
- Click File > Save.
- Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
- Click the Run Test button that appears on the
testAtTestSetupMethodsRule
method.
Code Highlights
Notice this third test method passes, even though it’s not creating data. In this test method, you’re relying on the platform to pre-execute the @TestSetup
method, which creates the opportunity you need for the test to pass. Note, even though the @TestSetup
method creates the test data, you still have to query for that data within your test. This can be confusing when you first start using @TestSetup
methods.
CSV Data Files
Using the testSetup
methods allows for concise, focused tests, but we can go yet another step further with our test data. We can create and upload a CSV file as a static resource and generate test data directly from that CSV.
Imagine a scenario where you can expand the scope of your tests without changing a line of code. CSV data lets you add to or modify the data inputs your code deals with, without deploying code.
- Using Visual Studio Code, paste the following into a file and save it as
accountData.csv
.Name,Website,Phone,BillingStreet,BillingCity,BillingState,BillingPostalCode,BillingCountry sForceTest1,http://www.sforcetest1.com,(415) 901-7000,The Landmark @ One Market,San Francisco,CA,94105,US sForceTest2,http://www.sforcetest2.com,(415) 901-7000,The Landmark @ One Market Suite 300,San Francisco,CA,94105,US sForceTest3,http://www.sforcetest3.com,(415) 901-7000,1 Market St,San Francisco,CA,94105,US
- In Setup, type
Static Resources
in the Quick Find box and select Static Resources.
- Click New.
- Name your static resource
accountData
.
- Click Choose File and select accountData.csv.
- Select Public for Cache Control.
- Click Save.
Now create a new test class in VS Code.
- Open VS Code.
- In the Explorer sidebar, right-click the folder classes, then choose SFDX: Create Apex Class. Name the class
CSVTests
and replace its contents with the following code.@IsTest private class CSVTests { @TestSetup static void loadTestDataFromStaticResource() { // GIVEN List<sObject> accounts = Test.loadData(Account.SObjectType, 'accountData'); } @IsTest static void testLoadAccountsFromStaticResource() { // WHEN List<Account> accts = [SELECT ID FROM Account]; // THEN Assert.isTrue(accts.size() == 3, 'Expected 3 accounts'); } }
- Click File > Save.
- Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
- Click the Run Test button that appears on the
testAtTestSetupMethodsRule
method.
Code Highlights
This code still uses an @TestSetup
method. But instead of using a testFactory or brute forcing it, you’re simply loading the CSV file. This makes for super-clean tests and lets you add to or modify your test data without deploying code.
Resources
-
GitHub: Example Test Factory Class
-
Apex Developer Guide: Using Test Setup Methods
-
Apex Developer Guide: Common Test Utility Classes for Test Data Creation
-
Apex Developer Guide: Loading Test Data