Skip to content
Home » Blog » Queueable Running User in Unit Test

Queueable Running User in Unit Test

I noticed that the Running User context of a Queueable Job in a unit test depends on who calls the Test.stopTest() method. While 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.

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. Basically you can produce a scenario that would never arise normally. If you don’t call Test.stopTest() at all, the job is executed at the end of the unit test, again in context of the running user of the test and not the user who 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 = 'testuser@invalid.test',
            EmailEncodingKey = 'UTF-8',
            LanguageLocaleKey = 'en_US',
            TimeZoneSidKey = 'GMT',
            LocaleSidKey = 'en_US',
            Alias = 'tst',
            IsActive = true
        );
    }
}

As pointed out by Adrian Larson in my Stack Exchange question this is probably as designed. However, I still think it’s not intuitive and does not follow what productive code would do.

Other Traps

LWC in Purgatory

Jun 12, 202402 min read

If you are delivering your SFDX projects using packaging you are likely aware of the challenges with moving metadata artefacts from one package to another. Particularly when moving up the dependency hierarchy.  There is this very neat “trick” for Unlocked Packages where you can temporarily remove metadata from a package…

Accidental Permissions

Apr 26, 202402 min read

Blowing the “Single Profile” approach out of the water When you install a package from AppExchange via the browser you get to choose whether to Install it for All Users, Only Admins, Specific Profiles or no-one. This really means if you want to grant full access to Classes, Pages, Fields…

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x