Isolating Apex Test Data from Actual Org/User’s Data!
The key to writing a good apex test case is to isolate test data from actual org/user data. If your test case in any way depends on org data, then it will fail for sure in deployments across different Salesforce orgs.
Unfortunately, Apex tests run in SYSTEM mode (the permissions and record sharing of the current user are not taken into account), so there is no isolation from the org/user’s data.
How do you isolate test data from an organization’s real data?
All your apex code should declare classes as “with sharing” unless you want to access all of the org data. This is important to ensure sharing rules work correctly. If you need to access all org data i.e. by passing sharing rules, then I suggest creating a new top-level or nested class with “without sharing” keywords before the class name. This without sharing class should be used to fire all such SOQLs, that need to bypass sharing.
Using System.runAs(User) one can create some isolation. But, System.runAs() is effective only if:
All Sobjects used in the Test case have “Private” sharing access. But that is not a possible and real scenario.
The test User profile has no profile overrides on sharing rules, like “Modify All Data” or “Modify/View All” permissions for Sobjects used in Test cases.
So, it is pretty hard to ensure that test cases will go well all the time. They will certainly fail because of a bad combination of sharing rules, profile permissions, and org data.
So, how to write stable Apex code + Apex Tests?
The only good way I found is
“your non-test apex code(trigger/controller) should handle test execution smartly. This means one has to write the trigger/controller code to filter records more specifically for the current user, when running in test mode.”
One can do this by just doing things:
All test codes should use System.runAs(<Mock User>) for creating quality test data. Note, here creating a mock user, who doesn’t already exist in the system is important for isolation. If you use some existing user, you might be able to see records owned by him/her.
Adding these criteria to all SOQL calls “WHERE OwnerId =:UserInfo.getUserId()” when Controller/Trigger is executed from Test Context. When this criteria is in place, only the test data will be visible to the trigger/controller code.
Next, in the code samples below, we will try to show how one can implement these two points in Apex.
Code Sample
This sample code fixture consists of:
A simple custom SObject named “TestObj__c” with “Private” Org-wide sharing access. This Sobject is just having a Text field named “SomeTextField”.
An apex class named “MyClass”, can be related to anything i.e. trigger or a Visualforce controller. This class:
tries to query custom object TestObj__c for a criteria.
has configuration attributes to tell, if the class is executing in a test context.
An apex test class that is written for MyClass. It
uses System.runAs etc to ensure tests run correctly in all orgs.
creates some TestObj__c records, for testing the MyClass’s query.
MyClass Code
Please note this class’s attributes like isRunningTest, this attribute is required until the Salesforce winter’11 release is GA, as winter’11 will give System.isRunningTest() method, which can be used anywhere in the test code to know if the code is running in a test context.
TestMyClass Code
Do we have a better way to write Apex Tests?
I know the above approach and code sample are messy and look weird, but this is what I can figure out to make sure that test cases run without crashing across the orgs and multiple deployments. I am sure it is not possible to do such owner ID replacements always, this can be because of many reasons like:
The project is too big and has many complex SOQLs.
For some reason, we can’t add the owner ID filter in SOQL where clause.
So, if you have any better ideas and ways to crack this problem. Please share with me.
New Idea - Apex tests shouldn’t get access to any org’s data!
So is there a way, really to isolate test cases from Salesforce Org data? I think nobody needs to access the org’s user data in apex test cases. Test cases should always run in isolation and there should be no way for them to access what’s the user data. This is because a single force.com app can be installed in many salesforce orgs, if the test cases rely on org data then they will for sure fail anywhere because of too much or too little data.
I have posted the new idea on Idea Exchange so that this isolation can be given by Salesforce to us in the coming releases. If you feel, I am correct this makes some sense. Then please promote this idea.