Understanding user mode in Apex tests
Starting with API version 67.0 (Summer '26 release), Apex tests default to running in user mode. Your tests now execute with the permissions and access rights of the user running the test. That is a good move for security, and it can also break tests that never accounted for a specific user context.
The shift changes how you have to test Apex code and the Permission Sets that go with it. System.runAs() is the method that gives you proper isolation and lets you check the security behavior of your code.
The role of System.runAs()
By default, tests run as the executing user, inheriting their object, field, and record access. Without isolation, a security flaw hides behind whatever access that user happens to have. System.runAs() lets you impersonate a specific user, so your test execution gets their defined permissions, and that is how you test the way your code behaves under different security profiles.
Defining security roles
Work out what security looks like from an administrative perspective before you write any enforcement code or testing strategy. Identify the user personas or roles responsible for managing specific data sets: who creates, who edits, who only consumes the information.
A quote calculation engine, for instance, might need a "Pricing Admin" persona with rights to manage pricing and discount rules, and a "Sales User" persona who consumes that data to generate quotes but cannot modify the rules themselves. Your Apex code enforces the distinction. Your tests should prove it with System.runAs().
Implementing System.runAs() in tests
Create the test users inside your test setup, assign each one the Permission Set it needs, then wrap both the data creation and the test execution in System.runAs().
In pseudocode:
@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 tests are what prove a Permission Set is actually restricted. Check that a user holding it cannot perform actions outside its 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
It is worth writing tests that assert general users lack access to objects or fields that should only be accessible in system mode. Confirm, for example, that neither permission set allows write operations to a logging object.
Leave a Comment