4

Use case - verifying that running user's permissions permit custom field to be created.

Assume that:

  • PermissionSet myPermSet grants edit permission on Obj__c.Fld__c
  • The test's running user does not have myPermSet assigned before the test is run.

Code under test

public class Foo {
  public void doStuff(Obj__c obj) {
     if (!Schema.SObjectType.Obj__c.Fld__c.isCreateable()) {throw new MyException(...);}
     insert obj;
  }
}

Testmethod

@IsTest
private class FooTest {
   @IsTest
   static void testDoStuff() {
      // Given permissions assigned to running user
      System.runAs(new User (Id = UserInfo.getUserId())) {
          insert new PermissionSetAssignment (
             AssigneeId = UserInfo.getUserId(),
             PermissionSetId = [SELECT Id FROM PermissionSet WHERE Name = 'myPermSet'][0].Id);
      }

      // When code-under-test executed (running user is UserInfo.getUserId() by definition)
      new Foo().doStuff(new Obj__c( Fld2__c = 'abc', ..));

      // Whoa!  exception is thrown even though PermSet granted running user create permission on Obj__c.Fld__c
     
    }
} 

So, even though the PermissionSet is successfully assigned to the running user of the testmethod, when the code-under-test doStuff(..) is executed, the isCreateable() test fails!?!

If the perm set is assigned to the running user via the UX (Permission Set | Manage Assignments), the code under test executes just fine; the isCreateable() test succeeds.

What is going on? How do I fix this?

1 Answer 1

3

Even though the Permission Set Assignment is inserted in a System.runAs context for the same user as executes the rest of the testmethod .. i.e. ...UserInfo.getUserId(), these perm set assignments don't stick.

The fix is to change the testmethod so the code-under-test is also executed in a System.runAs context

@IsTest
private class FooTest {
   @IsTest
   static void testDoStuff() {
      // Given permissions assigned to running user
      System.runAs(new User (Id = UserInfo.getUserId())) {
          insert new PermissionSetAssignment (
             AssigneeId = UserInfo.getUserId(),
             PermissionSetId = [SELECT Id FROM PermissionSet WHERE Name = 'myPermSet'}[0].Id);
      }

      // When code-under-test executed (using System.runAs this time)
      System.runAs(new User(Id = UserInfo.getUserId()) {
        new Foo().doStuff(new Obj__c( Fld2__c = 'abc', ..));

        // Obj__c.Fld__C is createable and object inserts. Hooray!
      }
    }
}

We use runAs() to avoid MIXED_DML errors doing setup objects and non-setup objects in the same Transaction. The example is trivialized as there could be lots of other stuff going on and the Perm Set assignment could be done faraway in some testutility class.

The System.runAs doc states

Generally, all Apex code runs in system mode, where the permissions and record sharing of the current user are not taken into account. 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.

You can use runAs only in test methods. The original system context is started again after all runAs test methods complete

From the revised testmethod, it would seem as though PermissionSet assignment done in one context doesn't carry forward into the original system context once the runAs completed but that two runAs contexts that use the same userId will share the effects of the PermissionSet assignment.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .