[SalesForce] How to test CRUD and FLS in unit tests

BACKGROUND

I am working on a managed package and it contains a custom object (X__c) with one custom field (X__c.Fld__c). In the controller I check if a user has Read access to both the object and field:

MyController.cls

public class MyController
{
  public MyController() {}

  public List<X__c> selectX()
  {
     if (X__c.SObjectType.getDescribe().isAccessible()) {
       if (X__c.Fld__c.getDescribe().isAccessible())
       {
         return [SELECT Id, Fld__c FROM X__c];
       }
     }

     return null;
  }
}

And the test class.

MyControllerTest.cls

public class MyControllerTest
{
    @isTest
    static void verifySelectX()
    {
        // Given
        X__c[] xRecords =
            (List<X__c>) TestFactory.create(new X__c(), 2);

        // When
        xRecords = new MyController().selectX();

        // Then
        System.assertNotEquals(null, xRecords);
        System.assertEquals(2, xRecords.size());
    }

    @isTest
    static void verifySelectXNoAccess()
    {
        // Given
        X__c[] xRecords =
            (List<X__c>) TestFactory.create(new X__c(), 2);

        // Create a user which will not have access to the test object type
        User testUser = createUser('Chatter External');
        if (testUser == null) {
            // Abort the test if unable to create a user with low enough acess
            return;
        }

        // When
        System.runAs(testUser)
        {
            xRecords = new MyController().selectX();

            // Then
            System.assertEquals(null, xRecords);
        }
    }
}

QUESTION

When I deploy the project metadata to our packaging org, it fails because the verifySelectX() test doesn't pass. It's due to the user (admin) who runs the test on deployment does not have access to the object and its fields yet. So can I make sure that the tests pass? I thought this is a common problem, but I couldn't find any info on it.

Best Answer

There are a few different ways to attack this problem, but the most common is to create a user with permissions, and then use System.runAs to execute your tests.

I normally grant permissions to my packages via permset and not profiles, so I create a user, give it a permission set that I created, and then run the tests. That way I don't depend on the admin user (or any installed org's profiles) for my tests to be successful and I only depend on things that are included in my package.

For example:

@IsTest
static void myTestPermSet() {
  User myUser = new User(<UserPropertiesGoHere>);
  insert u;

  PermissionSetAssignment psa = new PermissionSetAssignment (PermissionSetId = myPermissionSetId, AssigneeId = u.id);

  insert psa;    

  System.RunAs(u) {
    Test.startTest();

    <test your code here>

    Test.stopTest();

    <asserts go here>
  }
}
Related Topic