Skip to main content

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

Note

To execute the tests that you’ll write in this module, you’ll use VS Code. Complete Quick Start: Visual Studio Code for Salesforce Development  to learn how to install it, create a project, and connect the  project to your Trailhead playground.

Brute Force

This method consists of you creating and inserting the necessary records manually on each test. 

  1. Open VS Code.
  2. 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.
  3. Enter DataGenerationTests as the name and accept the default directory.
  4. 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');
      }
    }
  5. Click File > Save.
  6. 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.
  7. Click the Run Test button that appears on the testBruteForceAccountCreation method of the Apex Class you have just created and deployed.

    Run test button.
  8. 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.

  1. Go back to your test class in VS Code.
  2. 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;
  3. 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.

  1. Open VS Code.
  2. In the Explorer sidebar, right-click the folder classes, then choose SFDX: Create Apex Class.
  3. Name the class TestFactory and accept the default directory.
  4. 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;
      }
    }
  5. Click File > Save.
  6. 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.

  1. Open VS Code.
  2. In the Explorer sidebar, click the folder classes.
  3. Select the class DataGenerationTests and open it.
  4. 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');
    }
  5. Click File > Save.
  6. Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
  7. 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.

  1. Go back to your DataGenerationTests class in VS Code.
  2. 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;
    }
  3. Click File > Save.
  4. Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
  5. 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.

  1. Go back to your DataGenerationTests class in VS Code.
  2. Find the line of code that reads:
    Assert.areEqual(5, contacts.size(), 'Was expecting to find 5 contacts');
  3. Modify that line of code to specify 6 contacts, like this.
    Assert.areEqual(6, contacts.size(), 'Was expecting to find 6 contacts');
  4. Click File > Save.
  5. Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
  6. 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.

  1. Go back to your DataGenerationTests class in VS Code.
  2. 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');
    }
  3. Click File > Save.
  4. Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
  5. 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. 

  1. 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
  2. In Setup, type Static Resources in the Quick Find box and select Static Resources.
  3. Click New.
  4. Name your static resource accountData.
  5. Click Choose File and select accountData.csv.
  6. Select Public for Cache Control.
  7. Click Save.

Now create a new test class in VS Code.

  1. Open VS Code.
  2. In the Explorer sidebar, right-click the folder classes, then choose SFDX: Create Apex Class. Name the class CSVTestsand 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');
      }
    }
  3. Click File > Save.
  4. Right-click the file you’re working on, then choose SFDX: Deploy Source To Org.
  5. 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

無料で学習を続けましょう!
続けるにはアカウントにサインアップしてください。
サインアップすると次のような機能が利用できるようになります。
  • 各自のキャリア目標に合わせてパーソナライズされたおすすめが表示される
  • ハンズオン Challenge やテストでスキルを練習できる
  • 進捗状況を追跡して上司と共有できる
  • メンターやキャリアチャンスと繋がることができる