function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Phil WPhil W 

How to run two batches from same test start/stop with different users?

I have a batch process that prevents two different "concurrent" executions of the batch for a given user by maintaining some state (in the databse) that is set in start and cleared in finish (actually using the user for the created AsyncApexJob instances). During start processing, the state is queried from the database; if there is an entry already for the current user the batch is aborted, using System.abortJob, and an empty query locator is returned.

I am trying to test that two different users can successfully execute the batch via the use of the following:

Id profileId = UserInfo.getProfileId();

List<User> fakeUsers = new List<User> {
        new User(Alias = 'X', Email='X@testorg.com', EmailEncodingKey='UTF-8', FirstName = 'Jim', LastName='Testing', LanguageLocaleKey='en_US', LocaleSidKey='en_US', ProfileId = profileId, TimeZoneSidKey='Europe/London', UserName='X@testorg.com'),
        new User(Alias = 'Y', Email='Y@testorg.com', EmailEncodingKey='UTF-8', FirstName = 'Fred', LastName='Testing', LanguageLocaleKey='en_US', LocaleSidKey='en_US', ProfileId = profileId, TimeZoneSidKey='Europe/London', UserName='Y@testorg.com')
};

Test.startTest();

// Simulate running both together under different user accounts
Id id1;
Id id2;

System.runAs(fakeUsers[0]) {
    MyBatch b1 = new MyBatch();

    id1 = Database.executeBatch(b1);
}

System.runAs(fakeUsers[1]) {
    MyBatch b2 = new MyBatch();

    id2 = Database.executeBatch(b2);
}

Test.stopTest();

Unfortunately it seems that both of the batches still get run with the same user (the actual user running the test rather than either of the fake users). I suspect this is because of the way batches are actually executed during the Test.stopTest method invocation, rather than at some asynchronous time.

Have I come across a bug in the way batches are run in a given user context during testing? Is there a workaround I can use?
Best Answer chosen by Phil W
Youstina GuirguisYoustina Guirguis
I have raised a salesforce ticket with Premier support to check this issue as I am facing it in my org and they directed me to your bug report "idea".

Will update this thread if I find anything useful!

All Answers

David @ ConfigeroDavid @ Configero
System.runAs(user) {
    Test.startTest();

    // Execute batch job here

    Test.stopTest();
}

You have to call Test.startTest() and Test.stopTest() inside the System.runAs() call and make one unit test method per runAs() desired.

Reason:
Because a batch process executes asynchronously, and calling it within a Test.start/stop means it will force execution of all asynchronous jobs [synchronously] when Test.stopTest() is called.  This also means your batch jobs, while technically 'requested' as a different user, are executing as the original user who is running the tests.  When you force runAs { Test.start; batch job; Test.stop; } then it is forcing the job to be run while still inside the context of the runAs user.

The downfall of this means you will need one test for each runAs() context you wish to use in regards to batch jobs, because a unit test method only allows one pair of Test.startTest() / Test.stopTest() calls to be utilized.

Please mark this answer as correct if it helped you.
Phil WPhil W
David,

Thanks for taking the time to respond. You have confirmed my concern (as mentioned in my original posting) - the Salesforce infrastructure fails to carry forward the contextual user information from when the execute is triggered in the test. This is, in my opinion, a bug in their implementation. I will see if I can raise this somehow.

Regarding doing two separate Test.startTest()/stopTest() scopes, this would clearly not support the scenario I have described; the test must simulate the "concurrent" rather than "sequential" execution of the two batches. Again, I realize that the way tests handle asynchronous processing isn't truly asynchronous (with the batches actually running during the Test.stopTest() invocation), but I would hope that it would simulate it enough (e.g. perform start on each pending batch, then iterate the executes (as blocks or interleaved) and finally perform the finish for each batch) to support such test scenarios.

Thanks again for your time,

Phil
Phil WPhil W
As an update on this; it does appear that the batches all do get started "in parallel" within stopTest - i.e. the two batch starts are called before either of the batch finishes are called, so the issue is scoped to a failure to remember the contextual user for the execution.
Phil WPhil W
I have removed my "best answer" from this thread in the hope that a Salesforce support agent might actually look in on this thread and consider escalating this to a support case (I tried to do this but apparently my company doesn't have the necessary partner relationship to allow me to raise developer-related cases).

Here is a summary of the issue as I see it:

When writing an Apex test case, code of the form:
 
Test.startTest();

System.runAs(someUser) {
    Database.executeBatch(someBatch);
}

Test.stopTest();

Does not work correctly. The contextual "someUser" for the batch execution gets forgotten, and the user in scope when the Test.stopTest method is called is used instead. I suspect this relates to the way that the test infrastructure postpones asynchronous component execution to run when Test.stopTest is called, where the latter doesn't complete until all async processing has completed.

This seems, to me, to be a "simple" bug, where the delayed execution description does not include the contextual user detail, therefore resulting in the wrong user being used for such executions.

I would like to see this fixed in an up-coming version of Salesforce.
Phil WPhil W

Hey Salesforce support agents, could you have a look at this one and raise a support ticket please? Note that (since I need to simulate two parallel executions of batch processes run by different users) I cannot change the way the scopes are nested:

Test.startTest();

System.runAs(someUser) {
    Database.executeBatch(someBatch);
}

System.runAs(someOtherUser) {
    Database.executeBatch(someOtherBatch);
}

Test.stopTest();

It needs to work this way to support my testing.
David @ ConfigeroDavid @ Configero
Phil,

You will need to post this as an Idea at https://success.salesforce.com/ideaSearch for any internal reps to have visibility on it, as they won't be pulling items from a support forum here.  Please check there and see if the idea already exists to upvote it, or create a new one otherwise.
Phil WPhil W
Hi David,

Thanks for the info. This actually doesn't align with what I was told by support when they closed the ticket I had raised. Specifically, they said (note the piece in bold that I have highlighted):
 
"Developer support is currently available only to our premier customers and partners. We apologize for any impact this may be having on you. As you have Standard support, we will close the case at this time.

We support our standard customers and partners through the developer support boards at https://developer.salesforce.com/. If you have a developer support question, are looking for technical documentation, best practices, code samples and other ways to speed your development time the developer forums are the place for just that. We have dedicated support agents that work the boards to answer questions and review questions to ensure that you get a response."

From my perspective, this is a bug report not an idea suggestion. However, I cannot raise bug reports directly, only on the forums, as per the above quote from the level 1 support team in Salesforce. Pretty silly of them really.

Phil
 
David @ ConfigeroDavid @ Configero
Phil,

Premiere Support is different from the Ideas website I sent to you.  You will not get a change in the Salesforce Platform backend by creating a question on this forum.  The only way you have a chance of that happening is to submit it at the above website, which can be done by anyone and does not require Premiere Support.
Phil WPhil W
(btw, could not find an equivalent "idea". See https://success.salesforce.com/ideaView?id=0873A000000lJyqQAE for my submission.)
Phil WPhil W
David,

Thanks again. I am clear on the difference between Premier Support and the Ideas website. Whilst I understand that Salesforce don't want a flood of "help me!" type support calls, they should ensure that their communications contain accurate information; they clearly gave the wrong impression by what they told me when closing the ticket I had raised.

Please vote for my bug report "idea".

Phil
Youstina GuirguisYoustina Guirguis
I have raised a salesforce ticket with Premier support to check this issue as I am facing it in my org and they directed me to your bug report "idea".

Will update this thread if I find anything useful!
This was selected as the best answer
Phil WPhil W
Saw your comment on the "idea" and replied there.
Youstina GuirguisYoustina Guirguis
Salesforce has responded to my case which helped me! here is their response:
 
"Any asynchronous code included within Test.startTest and Test.stopTest is executed synchronously after Test.stopTest." and  "The system method runAs enables you to write test methods that change the user context to an existing user or a new user so that the user’s record sharing is enforced.The original system context is started again after all runAs test methods complete."

Which means whatever you write inside the System.runAs method will be executed in that newly created user's context. Once that block is finished , the statements following that goes into system context.

Refer :
Async code statement : https://trailhead.salesforce.com/en/content/learn/modules/asynchronous_apex/async_apex_batch

RunAs Method doc : https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_tools_runas.htm

You need to move your Test.startTest() and Test.stopTest() inside the runAs call. This is because since the batch process is asynchronous, it can only be guaranteed to have executed at the moment Test.stopTest() is invoked.