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

UoW’s Dirty Little Secret

Mar 15, 202324 min read

Did you know that you have to be super careful with registering records as dirty with a Unit of Work? If you happen to register the same record a second time you can lose your original registered updates. This is especially likely when you are trying to use the UOW…

Wasting Time in Disabled fflib Trigger Handlers

Jun 27, 202304 min read

Did you know that you can dynamically disable fflib_SObjectDomain Trigger Handlers? You did? Ok then, did you know that when you do disable them their constructors run anyway? So be careful not to put any (expensive) logic inside those. For instance, I had some common related records that needed to…

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