We have scenario where we are trying to create test data in tetsSetup method. We wrote a test data creation script in Util method and marked it as future and calling it from testsetup method. Is it efficient way to create test data in test class?


I would say no. This sounds like an attempt at "clever" programming (which I'll get back to).

If you're using an @future method (or any async method) to do test setup, you'd need to wrap the call to your setup inside Test.startTest() and Test.stopTest() to ensure that the async method actually executes before your test ends (in effect, making it a synchronous call).

If you're doing this outside of (or without) an @testSetup annotated method, then you're probably wasting time, because you'd be creating your test data again in each test method.

If you're attempting to do this in an @testSetup annotated method, then not only are you wasting effort (turning an async call into a sync call), you're also wasting the one pair of Test.startTest() and Test.stopTest() that you're allowed per test method.

Personally, I use Test.startTest() and Test.stopTest() to separate the execution of the SUT (system under test) from the rest of the test method (the setup and queries/assertions that come after executing the SUT). Using the start/stop test methods around as little code as possible means that you have better information about the limit utilization of the thing that you're actually testing. While not a big issue in smaller scale environments, being able to know precisely how much of your governor limits the code being tested consumes can be a godsend when working on medium to large scale environments.

All in all, this reeks of "clever" programming (a derogatory term that has the implication that you're trying to exploit a feature or work around a problem without adequate understanding of the system or features that you're using).

I think it wise to keep in mind that premature optimization can be an enormous time-sink, and is often not worth the effort. If you are going to optimize, you really ought to measure values first (with Salesforce, that'll generally be governor limit utilization), analyze your existing code, and then define targets for your measured values.

The best practice that Salesforce offers to us at this time (Spring '18 / APIv42.0) is...

  • Use a utility class to generate test data that conforms to your validation rules for each object
  • And use that class in an @testSetup annotated method (using just synchronous code) in your test class so that the test data is only created once per test class
    • Also, make any modifications that are required for all of the tests in your test class
    • With "Fine" adjustments to test data performed in each test method (if required)
  • And query the test data as required in your test methods (querying is faster than re-creating data for each test)
  • Use Test.startTest() and Test.stopTest() to give yourself a new set of governor limits for executing the code that you're trying to test

If you're running into issues with governor limits when creating your test data, then I'd say it's time to start looking into a trigger framework (if you haven't already) that allows you to programmaticaly disable/enable triggers. A few such currently available trigger frameworks (I've only done some cursory googling) are:

While this will increase the amount of work that you need to put into test setup (if you rely on triggers to set some of the state of the test environment, disabling triggers means you'll need to do that setup explicitly), it will also mean that your tests rely on less code, and are therefore closer to being "true" unit tests. By avoiding as many triggers as possible in your test setup, you're also (hopefully) reducing the amount of governor limits consumed by your test setup. It also means that you have greater control over the test environment (which is basically just a euphemism for saying you need to put more work into setting up your test environment).

| improve this answer | |
  • FYI, at least for now, future calls (and others), do not actually require you to call Test.stopTest. See this question. – sfdcfox Mar 9 '18 at 23:37

There's no point in calling a future method in @testSetup. At best, you're wasting governor limits for no good reason, and at worst, it won't solve whatever problems you're having. I did realize some time ago that governor limits that are used up in @testSetup do count towards your other unit test's governor limits, which are presumably the reason why you're asking this question.

The correct solution is to instead call Test.startTest() and Test.stopTest() in your @testSetup method. This will cause consumed limits to be reset for anything you do between Test.startTest() and Test.stopTest(). You'll still need to re-query the data for each unit test, but you'll have a lot more governor limit left over using this technique.

In other words, your unit test classes should look like this:

@isTest class MyUnitTest {
  @testSetup static void testSetup() {
    // Use DML operations here as necessary
    insert (testAccount = new Account(Name='Test'));
    insert (testContact = new Contact(LastName='Test',AccountId=testAccount.Id));
    // ...
  // Static Data
  static Account testAccount;
  static Contact testContact;
  // ...
  static void loadTestData() {
    testAccount = [SELECT Name, Industry FROM Account];
    testContact = [SELECT FirstName, LastName, Email FROM Contact];
    // ...
  // Now, for each unit test
  @isTest static void testNumber1() {
    // Do any extra initialization here
    // Test whatever you need to test here
    // Do your assertions here

Using this design will free up some governor limits from @testSetup, giving you overall faster tests and without using "clever" designs.

You'll still be relatively limited to how much you can query, but in most cases, it should be more than sufficient. Using any sort of asynchronous code won't help you get more governor limits, because asynchronous code is still called synchronously and will consume limits like normal.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.