Metadata-Driven Personas

Metadata-Driven Personas: Taming the Complexity of Salesforce Permissioning
In production, the introduction of multiple permissioning mechanisms—profiles, permission sets, permission set
groups, custom permissions, muting permissions, queues, public groups, etc. has created a maze that’s hard to
navigate.
With so many elements in play, administrators often find themselves copying an existing user rather than taking the
time to understand the full combination of permissions that are actually applied.
The Problem in Detail
-
Complexity and Copy-Paste:
With profiles, permission sets, and groups, it’s far easier to simply copy an existing user rather than analyze the exact combination of permissions. The danger here is that these “cloned” personas aren’t always consistent or well understood. -
Permission Set Proliferation:
The ease of creating new permission sets leads to duplicates and nearly identical sets being created, further muddying the waters when trying to establish what a “real” persona is. -
No Motivation to Remediate:
If nothing visibly breaks, there’s little drive to fix or streamline these permission configurations. As a result, your production environment continues to diverge from what is tested. -
Mismatch Between Unit Tests and Production:
Unit tests end up with artificially defined personas that don’t match the reality of the production users—resulting in tests that pass without reflecting the true security constraints in play.
What is needed is a consistent and repeatable method for creating test and production users.
A Metadata-Driven Approach to Defining Personas
To resolve these issues, we can define a custom metadata type that captures the complete definition of a user persona and use this to generate users both winthin our unit tests and for production user setup.
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
<label>User Persona</label>
<protected>false</protected>
<values>
<field>PersonaName__c</field>
<value xsi:type="xsd:string">Standard_User</value>
</values>
<values>
<field>ProfileName__c</field>
<value xsi:type="xsd:string">Standard User</value>
</values>
</CustomMetadata>
And then utilising a Factory class:
public class PersonaFactory {
public static User createUserFromPersona(String personaName) {
UserPersona__mdt persona = [
SELECT PersonaName__c, ProfileName__c, PermissionSetNames__c,
LocaleSidKey__c, TimeZoneSidKey__c, LanguageLocaleKey__c
FROM UserPersona__mdt
WHERE PersonaName__c = :personaName
LIMIT 1
];
Profile p = [SELECT Id FROM Profile WHERE Name = :persona.ProfileName__c LIMIT 1];
String uniqueEmail = personaName + '@test.com';
User newUser = new User(
Alias = personaName.substring(0, Math.min(personaName.length(), 5)),
Email = uniqueEmail,
ProfileId = p.Id,
UserName = uniqueEmail
);
insert newUser;
return newUser;
}
}
We have now abstracted the user creation process into a factory class that supports "named" personas, these are consistent across environments as the metadata can be deployed forwards through your pipeline in a consistent manner - as your Persona's require changing due to additional functionality or controls, so the metadata changes and more importantly can be tested in a consistent manner.
@isTest
private class TestUserPersonaIntegration {
static testMethod void testUsersFromMetadata() {
List personaNames = new List{'Standard_User', 'Read_Only_User'};
for (String personaName : personaNames) {
User testUser = PersonaFactory.createUserFromPersona(personaName);
System.runAs(testUser) {
// Test logic here
}
}
}
}
There is one final improvement that we can introduce that will improve the performance of our unit tests significantly. If we use a queueable class to execute the factory - we will be shifting the processing from our current thread into a system threading model, this not only improves performance it also removes the impact of user creation from our transaction limits.
Introducing a DTO & Queueable Approach to User Creation
We’ll modify our factory method to leverage a DTO-based Data Factory with a Queueable handler
public class UserDTO {
public String personaName;
public String profileName;
public String permissionSetNames;
public UserDTO(String personaName, String profileName, String permissionSetNames) {
this.personaName = personaName;
this.profileName = profileName;
this.permissionSetNames = permissionSetNames;
}
}
For more efficiency, the user creation process is offloaded to a Queueable class, as long as the queable is executed in your tests setup method it will be guaranteed to have been executed before your unit tests run.
public class UserFactoryQueueable implements Queueable {
private List userDTOs;
public UserFactoryQueueable(List userDTOs) {
this.userDTOs = userDTOs;
}
public void execute(QueueableContext context) {
List usersToInsert = new List();
for(UserDTO dto : userDTOs) {
Profile p = [SELECT Id FROM Profile WHERE Name = :dto.profileName LIMIT 1];
String uniqueSuffix = String.valueOf(DateTime.now().getTime());
String uniqueEmail = uniqueIdentifier + '_' + uniqueSuffix + '@test.com';
User newUser = new User(ProfileId = p.Id, UserName = uniqueEmail);
usersToInsert.add(newUser);
}
insert usersToInsert;
}
}
Summary
The complexity of Salesforce’s permissioning ecosystem has led to a divergence between test and production environments. By adopting a metadata-driven approach and leveraging a DTO-based Queueable handler, we gain:
-
Single Source of Truth:
Personas are centrally defined in metadata. -
Consistency:
Tests and production processes remain aligned. -
Decoupling from Execution Limits:
User creation is offloaded to a Queueable job. -
Enhanced Maintainability:
Updates are centralised, reducing complexity.
This approach ensures that tests reflect real-world scenarios, optimising security and maintainability in Salesforce environments.
There is a final enhancement that Ill leave you to add to support a complete solution. A secondary batch process that comapres metadata entries against actual user settings will identify any unexpected user permissioning - in this way you can provide an ongoing audit and control mechanisim.
Happy coding, and may your tests always pass!