1

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 = 'testuser@invalid.test',
            EmailEncodingKey = 'UTF-8',
            LanguageLocaleKey = 'en_US',
            TimeZoneSidKey = 'GMT',
            LocaleSidKey = 'en_US',
            Alias = 'tst',
            IsActive = true
        );
    }
}
2

This is expected behavior if you carefully read the documentation on how Test.stopTest() works.

stopTest()

Each test method is allowed to call this method only once. Any code that executes after the stopTest method is assigned the original limits that were in effect before startTest was called. All asynchronous calls made after the startTest method are collected by the system. When stopTest is executed, all asynchronous processes are run synchronously.

Crucially, to reiterate:

When stopTest is executed, all asynchronous processes are run synchronously.

Any asynchronous code which was enqueued runs synchronously when the method is called. Therefore, it follows logically that the user who calls Test.stopTest() would be the context user.

1
  • I see what you are saying. I believe that the fundamental implementation of how it really runs is behind this. I was looking at it as "ran synchronously" as in "right away, in the transaction of the unit test". Still expected the job to ran under the user who queued it though.
    – alesremta
    Jun 15 at 21:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.