Skip to main content
SFDC Developers
Apex

Apex Test User Mode: System.runAs for Permissions

Vinay Vernekar · · 4 min read

Understanding User Mode in Apex Tests

Starting with API version 67.0 (Summer '26 release), Apex tests default to running in user mode. This means your tests now execute with the permissions and access rights of the user running the test. While this is a positive step for security, it can introduce new failures for tests that haven't historically accounted for specific user contexts.

This shift necessitates a robust approach to testing Apex code and associated Permission Sets. The System.runAs() method is crucial for achieving proper isolation and validating security aspects of your code.

The Role of System.runAs()

By default, tests run as the executing user, inheriting their object, field, and record access. This lack of isolation can obscure security flaws. System.runAs() allows you to impersonate a specific user, granting your test execution their defined permissions. This is vital for testing how your code behaves under different security profiles.

Defining Security Roles

Before diving into Apex code security enforcement or testing strategies, define what security looks like from an administrative perspective. Identify user personas or roles responsible for managing specific data sets (e.g., creating, editing, or only consuming information).

For instance, a quote calculation engine might require a "Pricing Admin" persona with rights to manage pricing and discount rules, and a "Sales User" persona who consumes this data to generate quotes but cannot modify the rules themselves. Your Apex code enforces these distinctions, and your tests should validate them using System.runAs().

Implementing System.runAs() in Tests

To test these distinct permission sets, you can create test users within your test setup, dynamically assign them the appropriate Permission Sets, and then use System.runAs() to wrap your data creation and test execution logic.

Here's a pseudocode example:

@testSetup
static void setup() {
    // Create test users with specific permission sets
    createStandardUserWithPermissionSet('QuoteApp_PricingAdmin', PRICING_ADMIN_LASTNAME, PRICING_ADMIN_EMAIL);
    createStandardUserWithPermissionSet('QuoteApp_SalesUser', SALES_USER_LASTNAME, SALES_USER_EMAIL);

    // Execute as Pricing Admin to set up core pricing data
    System.runAs(getTestPricingAdminUser()) {
        insert new PriceConfig__c(/* required fields */);
        insert new PriceRule__c(/* required fields */);
    }

    // Execute as Sales User to set up transactional records
    System.runAs(getTestSalesUser()) {
        insert new Quote__c(OwnerId = salesUser.Id, /* ... */);
        insert new QuoteLine__c(/* ... */);
    }
}

@IsTest
static void addQuoteLine_asSalesUser() {
    System.runAs(getTestSalesUser()) {
        // Given: Query Quote created by Sales User
        Quote__c quote = [SELECT Id FROM Quote__c WHERE OwnerId = :salesUser.Id LIMIT 1];

        // When: Calculate Quote
        Test.startTest();
        QuoteService.addLine(quote.Id, /* product, qty, etc. */);
        Test.stopTest();

        // Then: Query Quote line calculated
        QuoteLine__c line = [SELECT Id, UnitPrice__c FROM QuoteLine__c WHERE Quote__c = :quote.Id LIMIT 1];
        System.assertNotEquals(null, line.UnitPrice__c, 'Line should be priced using readable config/rules');
    }
}

@IsTest
static void createPriceRule_asPricingAdmin() {
    System.runAs(getTestPricingAdminUser()) {
        // When: Creating a pricing rule
        Test.startTest();
        Id ruleId = PricingAdminService.createPriceRule(new PriceRule__c(/* ... */));
        Test.stopTest();

        // Then: Pricing rule successfully created
        System.assertNotEquals(null, ruleId);
    }
}

Negative Security Testing

Negative testing is crucial for validating that Permission Sets are correctly restricted. Ensure that users assigned a specific Permission Set cannot perform actions outside their scope.

This test verifies that the "Sales User" permission set does not grant access to create pricing rules:

@IsTest
static void createPriceRule_deniedForSalesUser() {
    User salesUser = getTestSalesUser();
    // Given: Sales user
    System.runAs(salesUser) {
        Test.startTest();
        try {
            // When: Attempting to create pricing rule
            PricingAdminService.createPriceRule(new PriceRule__c(/* ... */));
            System.assert(false, 'Expected a security or access failure');
        } catch (Exception e) {
            // Then: Security enforced / also see DMLException methods
            System.assert(
                e.getMessage().toLowerCase().contains('insufficient') ||
                e.getMessage().toLowerCase().contains('access'),
                'Unexpected: ' + e.getMessage()
            );
        }
        Test.stopTest();
    }
}

Broader Security Assertions

Consider writing tests that assert that general users lack access to objects or fields that should only be accessible in system mode. For example, confirm that neither permission set allows write operations to a logging object.

Key Takeaways

  • Apex tests now run in user mode by default, emphasizing the importance of testing with specific user permissions.
  • System.runAs() is the primary mechanism for impersonating users and testing Permission Sets in Apex.
  • Define distinct user personas and their associated permissions before implementing security in Apex and tests.
  • Implement both positive and negative security tests to validate that users have appropriate access and are denied unauthorized actions.
  • Consider system-level security assertions to ensure sensitive operations are not accessible by general users.

Share this article

Get weekly Salesforce dev tutorials in your inbox

Comments

Loading comments...

Leave a Comment

Trending Now