4

Unfortunately it seems that there is some undocumented behaviour with System.runAs and creating users in Apex tests. In particular we observed, with API 45:

  1. If we run a test class that we do not define without sharing if we pass the ProfileId in the constructor to the new User, this is not saved.
  2. If we try to set it later in a test class not defined without sharing after like so user1.ProfileId = [SELECT Id FROM Profile WHERE Name='Standard User'].Id we get an error that says that ProfileId is not writeable
  3. If we try in a class that has a without sharing then we get an error when we invoke System.runAs: REQUIRED_FIELD_MISSING, Required fields are missing: [ProfileId]: [ProfileId]

Here, for example, the code of the first case:

public class UserTest {

private static User buildUser(Profile p, String username, String alias){
    User user1 =  new User(Alias = alias, Username=username, LastName ='LM', 
     Email='example@example-213213.com',
     EmailEncodingKey='UTF-8',
     LanguageLocaleKey='en_US',
     LocaleSidKey='en_US', 
     TimeZoneSidKey='America/Los_Angeles');
    user1.profileId  =  p.Id;
    System.debug('Returning user '+ user1);
    return user1;
}

@isTest
public static void example1(){
    Profile p = [SELECT Id FROM Profile WHERE Name='Standard User'];
    System.debug('Using profile ' + p);
    User u2 = buildUser(p, 'example1@sfdx-test.com', 'lm');
    System.runAs(u2){
        User u3 = buildUser(p, 'example2@sfdx-test.com','ceo');
        System.runAs(u3){
            System.debug([Select ID from User]);    
        }
    }
  }

}

The above example fails with:

UserTest.example1  System.SObjectException: Field is not writeable: User.ProfileId
                   /Users/user/Development/salesforce/sfdx-project/force-app/main/default/classes/UserTest.cls:11:1
                   /Users/user/Development/salesforce/sfdx-project/force-app/main/default/classes/UserTest.cls:22:1

What are the known limitations of System.runAs and why would we encounter such problems on a scratch org?

3
  • 4
    Please try to add some of the most obvious tags that cover big feature groups where possible. Also, can you please edit your post to include a question? Thanks!
    – Adrian Larson
    May 13, 2019 at 18:06
  • 2
    #1 is hard to believe -- in my just-conducted experiment testmethods v45 with (or without) sharing happily create users given a valid ProfileId
    – cropredy
    May 13, 2019 at 21:05
  • @cropredy I add exactly the code I run. There is even an obvious bug (the email is not taken from the arguments of the buildUser but is hardcoded) but is not even the point May 14, 2019 at 7:48

1 Answer 1

2

I don't have as thoroughgoing and explanation as I might like, but this behavior is a consequence of System.runAs() enforcing system permissions against the new running user and presenting that enforcement in a way that's rather confusing.

System.runAs() is documented to enforce sharing but not FLS or CRUD:

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 runAs method doesn’t enforce user permissions or field-level permissions, only record sharing.

However, system-level permissions are still applied, in the same way they would be if you actually ran the "outer" test as some user or another.

I modified your example class as follows:

@isTest
public class UserTest {
    private static User buildUser(String username, String alias, String profileName){
        Profile p = [SELECT Id FROM Profile WHERE Name=:profileName];
        return new User(Alias = alias, Username=username, LastName='LM', 
                               Email='example@example-213213.com',
                               EmailEncodingKey='UTF-8',
                               LanguageLocaleKey='en_US',
                               LocaleSidKey='en_US', 
                               TimeZoneSidKey='America/Los_Angeles',
                               ProfileId=p.id);
    }

    private static void runTest(String profileName) {
        User u2 = buildUser('example1@sfdx-test.com', 'lm', profileName);
        insert u2;

        System.runAs(u2){
            User u3 = buildUser('example2@sfdx-test.com', 'ceo', 'Standard User');
            insert u3;
            System.runAs(u3){
                System.debug([Select ID from User]);    
            }
        }
    }

    @isTest
    public static void test_with_standard_user(){
        runTest('Standard User');
    }

    @isTest
    public static void test_with_sysadmin_user(){
        runTest('System Administrator');
    }

    @isTest
    public static void test_with_custom_user(){
        runTest('Custom: Sales Profile');

    }
}

(I found that passing ProfileId directly to the constructor eliminated the discrepancy in behavior between without sharing and no sharing declaration, for simplicity's sake).

I modified Custom: Sales Profile to add system permissions successively until I found one that allowed the test test_with_custom_user() to complete, then backtracked to identify the most granular change that determines success or failure.

It's Manage Internal Users. If the Profile of the current context user doesn't have this permission (or one that overrides/requires it, like Manage Users), the insert fails; if it does, the insert succeeds.

3
  • 1
    note in OP, mocked users were never inserted - whereas in your answer (which makes sense) they are. The OP using runAs StandardUser to create new users is something that never would have occurred to me
    – cropredy
    May 14, 2019 at 13:47
  • 1
    @cropredy I believe there's an implicit insert that happens upon System.runAs(), based on what some of the exception messages look like. I made it explicit for clarity. Open to correction on that!
    – David Reed
    May 14, 2019 at 13:48
  • wow - learned something new!
    – cropredy
    May 14, 2019 at 13:50

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.