0

Sometimes when developing Apex, I've populated some static property. But then later -- most often in an Async or Test context -- I go to access it again and discover SFDC is behaving like it is accessing the property for the first time, so the value may be null, or a counter might be reset.

I don't expect static values to persist for eternity, but sometimes I find it shocking that the value no longer exists even though code is still executing.

Can anyone please explain the logic by which SFDC cleans these values? And is there anything we can do to maintain persistence a little longer?

In high-level pseudo-code, an example where this has caused a problem would be:

@IsTest
public class SomeTest {
    private static final User TEST_USER = TestUserFactory.create();

    @TestSetup
    private static void beforeTests() {
        insert TestAccountFactory.create();
    }

    @IsTest
    private static void someTest() {
        System.runAs(TEST_USER) {
            // DO SOMETHING
        }
        // Assert someting
    }
}

TestUserFactory consumes get() in the following class to obtain what is expected to be a unique FederatedId (as well as other unique values):

@IsTest
public class XFTY_DummyDefaultValueUniqueString implements XFTY_DummyDefaultValueIntf {
    private String prefix;
    private static Integer counter = 1;

    public XFTY_DummyDefaultValueUniqueString(String prefix) {
        this.prefix = prefix;
    }

    public Object get() {
        return this.prefix + ' ' + counter++;
    }
}

However, in practice we find that running someTest() causes TestUserFactory.create(); to execute twice because of the @TestSetup method.

On the second execution, it fails because XFTY_DummyDefaultValueUniqueString.counter is reset.

FWIW, we can make this test pass by changing the code to:

@IsTest
public class SomeTest {
    private static final User TEST_USER = TestUserFactory.create();
    private static final Boolean doInsert = true;
    private static final Account TEST_Account = TestAccountFactory.create(doInsert);

    @IsTest
    private static void someTest() {
        System.runAs(TEST_USER) {
            // DO SOMETHING
        }
        // Assert someting
    }
}

... which eliminates the second execution but now gives us an unused variable. (Or we could move Account creation into each test...)

... but the point is not making the test pass, but rather that XFTY_DummyDefaultValueUniqueString.counter seems to reset rather than persist.

  • 1
    This documentation should provide you with the details you are looking for. – Jayant Das Feb 14 '19 at 16:32
  • 2
    The behavior you will see is rather different in test context relative to regular context due to the way test "transactions" are structured. Can you give an example of a situation where the behavior is non-obvious? – David Reed Feb 14 '19 at 16:35
  • 2
    Yes please include a specific example or it is difficult to answer this question accurately and concisely. – Adrian Larson Feb 14 '19 at 16:44
6

Static variables are an attribute of a transaction, and will never persist longer than that transaction. They cannot be shared between multiple execution contexts, even if they are running in parallel (like unit tests). For example, two different unit test methods run, and each has their own version of the same static variable, or each call to an execute method in a Batchable class has their own version of the same static variable. If you want to keep it simple, each time a debug log finishes (typically EXECUTION_FINISHED), all static variables are lost.

In general, if you want data to persist longer than a single transaction, you need to save the data somewhere, either in platform cache, custom settings, a database record, or some form of external storage. There is no way to use any mechanism to store or share data between unit tests, although you can create test records using @testSetup (but the static variables are still reset, meaning you would need to query the data back from the database).

  • Well Database.Stateful is one exception (only one?) which when used can save state – user1974566 Feb 15 '19 at 12:25
  • Cheers for the response... Does TestSetup always reset all static variables? Can it make any difference where the TestSetup is placed within an Apex class (e.g. if static variables are listed after TestSetup, can their values survive)? – Brian Kessler Feb 17 '19 at 7:55
  • @user1974566 This answer directly speaks about static variables. There are no exceptions for static variables, as they are always reset between transactions. There are a few different methods, including Database.Stateful, for storing state, but this only works on instance variables. – sfdcfox Feb 17 '19 at 13:17
  • @BrianKessler As I said, static variables will never persist beyond a transaction; there are no exceptions to this rule. I actually even have an Idea that I created to save SOQL queries from testSetup, but there's no intent to allow this, as noted in a comment from an internal employee. – sfdcfox Feb 17 '19 at 13:21

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.