I noticed that the running User Context of a Queueable Job in a unit test depends on who calls the Test.stopTest() method. Or rather when is the job to be executed in the unit test context. I wonder if this is something that's documented somewhere?
When code is ran normally (trigger, anonymous, ...) the User context of the Queueable is the same as the user who put it on the queue. But in a unit test, it is actually the User in whose context the Test.stopTest() is executed (which is what causes the Async job to execute in unit tests.
So if you use System.runAs in a unit test to queue a job, the user context of the job will be different if you call Test.stopTest inside or outside of the System.runAs block. If you don't cal stop test at all, the job is executed at the end of the test, again in context of the running user of the test and not the user you queued the job.
It cost me some time debugging a failing test that used the UserInfo.getUserId() in a trigger with some dependent logic so I created a simple Queueable class to help me investigate. I might have just missed some piece of documentation, but hopefully this can save someone else some time.
public with sharing class QueueableClass implements Queueable {
public void execute(QueueableContext context) {
System.debug(LoggingLevel.DEBUG, 'InQueueable: ' + UserInfo.getName());
}
}
I ran this anonymous script to confirm the normal scenario
System.debug(LoggingLevel.DEBUG, 'InAnonymousAs: ' + UserInfo.getName());
System.enqueueJob(new QueueableClass());
Then I wrote some unit tests and noted who the running user is inside the Queueable job:
@IsTest
private class QueueableTest {
@IsTest
static void queueableRanAsRunningUser() {
//DEBUG|InQueueable: User User
User newUser = getTestUser();
System.debug(LoggingLevel.DEBUG, 'InTest: ' + UserInfo.getName());
Test.startTest();
System.runAs(newUser) {
System.debug(LoggingLevel.DEBUG, 'InRunAs: ' + UserInfo.getName());
System.enqueueJob(new QueueableClass());
}
Test.stopTest();
}
@IsTest
static void queueableRanAsTestUser() {
//DEBUG|InQueueable: TestUser
User newUser = getTestUser();
System.debug(LoggingLevel.DEBUG, 'InTest: ' + UserInfo.getName());
System.runAs(newUser) {
Test.startTest();
System.debug(LoggingLevel.DEBUG, 'InRunAs: ' + UserInfo.getName());
System.enqueueJob(new QueueableClass());
Test.stopTest();
}
}
@IsTest
static void queueableRanAsRunningUserNoStopTest() {
//DEBUG|InQueueable: User User
User newUser = getTestUser();
System.debug(LoggingLevel.DEBUG, 'InTest: ' + UserInfo.getName());
System.runAs(newUser) {
System.debug(LoggingLevel.DEBUG, 'InRunAs: ' + UserInfo.getName());
System.enqueueJob(new QueueableClass());
}
}
private static User getTestUser() {
Id profileIdToUse = UserInfo.getProfileId();
String rnd = String.valueOf((Math.random())).left(5);
return new User(
ProfileId = profileIdToUse,
LastName = 'TestUser',
Username = 'testuser' + rnd + '@invalid.test',
Email = '[email protected]',
EmailEncodingKey = 'UTF-8',
LanguageLocaleKey = 'en_US',
TimeZoneSidKey = 'GMT',
LocaleSidKey = 'en_US',
Alias = 'tst',
IsActive = true
);
}
}