Skip to content

Commit ba4fa73

Browse files
authored
feat/refactorTestFactory (#342)
* refactor(TestFactory): Refactors TestFactory Into SObjectFactory and several helper classes. Classes now adhere to SRP, and are in general better architected. * refactor(SObjectFactory): adding ApexDocs To various helper classes. * refactor(SObjectFactory): adding ApexDocs To various helper classes.
1 parent 355b202 commit ba4fa73

19 files changed

Lines changed: 871 additions & 0 deletions

.idea/dictionaries/project.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
-18 Bytes
Binary file not shown.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @description A test-only helper for manipulating users' permissions, permission sets and permission set groups
3+
*
4+
* Note: This class does *no* error handling. Because it's used in the setup and preparation of test data,
5+
* it's on the developer to ensure the methods are called with existing, valid data like permission set names.
6+
* If, for instance, a developer fat-fingers a permission set name, the query on ~ line 74 will fail, and throw
7+
* an exception.
8+
*/
9+
10+
@IsTest
11+
public class PermissionsHelper {
12+
/**
13+
* @description creates and inserts a permission set assignment record
14+
* @param userToAssignTo User the user whom the permission set will be applied to
15+
* @param permSetId Id The Id of the permission set to assign to the user.
16+
*/
17+
public static void assignPermissionSetToUser(
18+
User userToAssignTo,
19+
Id permSetId
20+
) {
21+
PermissionSetAssignment permissionSetAssignment = new PermissionSetAssignment(
22+
AssigneeId = userToAssignTo.Id,
23+
PermissionSetId = permSetId
24+
);
25+
insert permissionSetAssignment;
26+
}
27+
28+
/**
29+
* @description Assigns a permission set to a given user.
30+
* @param userToAssignTo User to assign the permission set to.
31+
* @param permSetName String name of the permission set.
32+
*/
33+
public static void assignPermSetToUser(
34+
User userToAssignTo,
35+
String permSetName
36+
) {
37+
assignPermissionSetToUser(
38+
userToAssignTo,
39+
UserFactoryHelper.fetchPermissionSetIdByName(permSetName)
40+
);
41+
}
42+
43+
/**
44+
* @description Generates a test permission set record - no permissions are added to it
45+
* @param permSetName String what to call your perm set
46+
* @param doInsert Boolean true if you want this to insert your perm set record.
47+
* @return PermissionSet the created permission set.
48+
*/
49+
public static PermissionSet createPermissionSet(
50+
String permSetName,
51+
Boolean doInsert
52+
) {
53+
PermissionSet newPermSet = new PermissionSet(
54+
Name = permSetName,
55+
Label = 'Test Permission Set'
56+
);
57+
if (doInsert) {
58+
insert newPermSet;
59+
}
60+
return newPermSet;
61+
}
62+
63+
/**
64+
* @description Enables a custom permission using a permission set
65+
* @param permissionName String name of the custom permission you want created
66+
* @param forUserId Id user to assign the custom permission to.
67+
*/
68+
public static void enableCustomPermission(
69+
String permissionName,
70+
Id forUserId
71+
) {
72+
PermissionSet permSet = createPermissionSet('TestPermSet', true);
73+
74+
Id customPermissionId = [
75+
SELECT Id
76+
FROM CustomPermission
77+
WHERE DeveloperName = :permissionName
78+
WITH SYSTEM_MODE
79+
LIMIT 1
80+
]
81+
.Id;
82+
83+
SetupEntityAccess permSetPermission = new SetupEntityAccess(
84+
ParentId = permSet.Id,
85+
SetupEntityId = customPermissionId
86+
);
87+
88+
PermissionSetAssignment permSetAssignment = new PermissionSetAssignment(
89+
AssigneeId = forUserId,
90+
PermissionSetId = permSet.Id
91+
);
92+
93+
insert new List<SObject>{ permSetPermission, permSetAssignment };
94+
}
95+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>63.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/**
2+
* @description a factory class for generating test data.
3+
* This class auto populates required fields and, by default, generates a plausible but fake ID for the record.
4+
* You can use this class directly, or use one of the associated builder classes for generating
5+
* complex data structures.
6+
*
7+
* This class has the ability to set default values for fields in 3, hierarchical ways.
8+
* 1. The default values passed into the SObjectFactory via the sObject prototype.
9+
* 2. The default values defined by the custom default class specified by the usingDefaultsClassName parameter.
10+
* 3. The default values found in the 'org-wide' defaults class defined in the SObjectFactoryDefaults' subclasses.
11+
*
12+
* It's important to note that *nothing* overwrites field values specified in the SObject prototype.
13+
*
14+
* Some profiling information:
15+
* | Action | Avg of 10 test executions |
16+
* |-----------|------------------------|
17+
* | Create 10 Single SObject w/ fake Id | 58ms |
18+
* | Create 10 Single SObjects w/ fake Id and Custom Defaults | 73ms |
19+
* | Create 10 Single SObjects & insert them | 550ms |
20+
* | Create list of 200 SObjects w/ fake Id | 115ms |
21+
* | Create list of 200 SObjects & insert them | 1216ms |
22+
*/
23+
@IsTest
24+
public with sharing class SObjectFactory {
25+
/**
26+
* @description Use the FieldDefaults interface to set up field/value keys you want to routinely impose on your
27+
* factory generated objects.
28+
*/
29+
public interface FieldDefaults {
30+
/**
31+
* @description Interface used by implementing classes to define defaults.
32+
* @return `Map<Schema.SObjectField, Object>`
33+
*/
34+
Map<Schema.SObjectField, Object> getFieldDefaults();
35+
}
36+
37+
/**
38+
* @description convenience method allowing you to specify just the prototype
39+
* @param prototype SObject any SObject
40+
* @return A created SObject with required fields populated
41+
*/
42+
public static SObject createSObject(SObject prototype) {
43+
return createSObject(prototype, null, false);
44+
}
45+
46+
/**
47+
* @description Convenience method for creating SObjects using just the prototype and a custom defaults class
48+
* @param prototype SObject - any constructed SObject
49+
* @param usingDefaultsClassName String - the name of the class to use for custom default values
50+
* @return A created SObject with required fields populated
51+
*/
52+
public static SObject createSObject(
53+
SObject prototype,
54+
String usingDefaultsClassName
55+
) {
56+
return createSObject(prototype, usingDefaultsClassName, false);
57+
}
58+
59+
/**
60+
* @description Creates an SObject with the given prototype and forceInsert flag.
61+
*
62+
* @param prototype The prototype SObject.
63+
* @param forceInsert Flag indicating whether to force insertion.
64+
*
65+
* @return The created SObject.
66+
*/
67+
public static SObject createSObject(SObject prototype, Boolean forceInsert) {
68+
return createSObject(prototype, null, forceInsert);
69+
}
70+
71+
/**
72+
* @description Creates an SObject with the given prototype, using defaults from the specified class, and optionally inserts it into the database.
73+
*
74+
* @param prototype The prototype SObject to be created.
75+
* @param usingDefaultsClassName The name of the class providing default values for the SObject.
76+
* @param forceInsert Indicates whether to insert the SObject into the database.
77+
*
78+
* @return The created SObject.
79+
*/
80+
public static SObject createSObject(
81+
SObject prototype,
82+
String usingDefaultsClassName,
83+
Boolean forceInsert
84+
) {
85+
prototype = internalCreateSObject(prototype, usingDefaultsClassName);
86+
if (forceInsert) {
87+
Database.insert(prototype, AccessLevel.SYSTEM_MODE);
88+
} else {
89+
prototype.Id = IdFactory.get(prototype);
90+
}
91+
return prototype;
92+
}
93+
94+
private static SObject internalCreateSObject(
95+
SObject prototype,
96+
String usingDefaultsClassName
97+
) {
98+
// ensure the defaults class is not null
99+
String functionalDefaultsClassName = String.isNotEmpty(
100+
usingDefaultsClassName
101+
)
102+
? usingDefaultsClassName
103+
: '';
104+
// First the specified defaults class
105+
prototype = SObjectFactoryHelper.applyCustomDefaults(
106+
prototype,
107+
functionalDefaultsClassName
108+
);
109+
// then the 'org-wide' defaults
110+
prototype = SObjectFactoryHelper.applyOrgWideObjectDefaults(prototype);
111+
return prototype;
112+
}
113+
114+
/**
115+
* @description Creates a list of SObjects based on the provided prototype and count.
116+
*
117+
* @param prototype The SObject prototype to base the created SObjects on.
118+
* @param count The number of SObjects to create.
119+
*
120+
* @return A list of created SObjects.
121+
*/
122+
public static List<SObject> createSObjects(SObject prototype, Integer count) {
123+
return createSObjects(prototype, count, null, false);
124+
}
125+
126+
/**
127+
* @description Creates a list of SObjects based on the provided prototype, count, and defaults class name.
128+
*
129+
* @param prototype The prototype SObject to base the new SObjects on.
130+
* @param count The number of SObjects to create.
131+
* @param usingDefaultsClassName The name of the class to use for default values.
132+
*
133+
* @return A list of created SObjects.
134+
*/
135+
public static List<SObject> createSObjects(
136+
SObject prototype,
137+
Integer count,
138+
String usingDefaultsClassName
139+
) {
140+
return createSObjects(prototype, count, usingDefaultsClassName, false);
141+
}
142+
143+
/**
144+
* @description Creates a list of sObjects based on the provided prototype, count, and forceInsert flag.
145+
*
146+
* @param prototype The prototype SObject to base the created objects on.
147+
* @param count The number of SObjects to create.
148+
* @param forceInsert Flag indicating whether to force insertion of the created SObjects.
149+
*
150+
* @return List of created SObjects.
151+
*/
152+
public static List<SObject> createSObjects(
153+
SObject prototype,
154+
Integer count,
155+
Boolean forceInsert
156+
) {
157+
return createSObjects(prototype, count, null, forceInsert);
158+
}
159+
160+
/**
161+
* @description Creates a list of SObjects based on a prototype, count, defaults class, and force insert flag.
162+
*
163+
* @param prototype The prototype SObject to clone.
164+
* @param count The number of SObjects to create.
165+
* @param usingDefaultsClassName The name of the defaults class to use.
166+
* @param forceInsert Whether to force insert the created SObjects.
167+
*
168+
* @return A list of created SObjects.
169+
*/
170+
@SuppressWarnings('PMD.ExcessiveParameterList')
171+
public static List<SObject> createSObjects(
172+
SObject prototype,
173+
Integer count,
174+
String usingDefaultsClassName,
175+
Boolean forceInsert
176+
) {
177+
List<SObject> createdSObjects = new List<SObject>();
178+
SObject constructedFromPrototype = internalCreateSObject(
179+
prototype,
180+
usingDefaultsClassName
181+
);
182+
for (
183+
Integer iterationCounter = 0; iterationCounter < count; iterationCounter++
184+
) {
185+
SObject clonedSObject = constructedFromPrototype.clone(false, true);
186+
createdSObjects.add(
187+
SObjectFactoryHelper.mutateCloneToRespectNameAndAutonumberRules(
188+
clonedSObject,
189+
!forceInsert,
190+
iterationCounter
191+
)
192+
);
193+
}
194+
SObjectFactoryHelper.insertIfForced(createdSObjects, forceInsert);
195+
return createdSObjects;
196+
}
197+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>63.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
@IsTest
2+
public class SObjectFactoryDefaults {
3+
// To specify defaults for objects, use the naming convention [ObjectName]Defaults.
4+
// For custom objects, omit the __c from the Object Name
5+
6+
/**
7+
* @description Default values for Account object
8+
*/
9+
public class AccountDefaults implements SObjectFactory.FieldDefaults {
10+
/**
11+
* @description Returns a map of field defaults for the Account object.
12+
*
13+
* @return A map containing the default values for specific Account fields.
14+
*/
15+
public Map<Schema.SObjectField, Object> getFieldDefaults() {
16+
return new Map<Schema.SObjectField, Object>{
17+
Account.Name => 'Test Account'
18+
};
19+
}
20+
}
21+
22+
/**
23+
* @description This class provides default field values for Contact objects.
24+
*/
25+
public class ContactDefaults implements SObjectFactory.FieldDefaults {
26+
/**
27+
* @description Returns a map of field defaults for the Contact object.
28+
*
29+
* @return A map where keys are Schema.SObjectField representing Contact fields and values are the default values for those fields.
30+
*/
31+
public Map<Schema.SObjectField, Object> getFieldDefaults() {
32+
return new Map<Schema.SObjectField, Object>{
33+
Contact.FirstName => 'First',
34+
Contact.LastName => 'Last'
35+
};
36+
}
37+
}
38+
39+
/**
40+
* @description This class provides default field values for Opportunity objects.
41+
*/
42+
public class OpportunityDefaults implements SObjectFactory.FieldDefaults {
43+
/**
44+
* @description Returns a map of default field values for Opportunity.
45+
*
46+
* @return Map<Schema.SObjectField, Object> A map containing default field values.
47+
*/
48+
public Map<Schema.SObjectField, Object> getFieldDefaults() {
49+
return new Map<Schema.SObjectField, Object>{
50+
Opportunity.Name => 'Test Opportunity',
51+
Opportunity.StageName => 'Closed Won',
52+
Opportunity.CloseDate => System.today()
53+
};
54+
}
55+
}
56+
57+
/**
58+
* @description Defaults for Case SObjectFactory
59+
*/
60+
public class CaseDefaults implements SObjectFactory.FieldDefaults {
61+
/**
62+
* @description Returns a map of field defaults for the Case object.
63+
*
64+
* @return A map where the key is the Schema.SObjectField and the value is the default value.
65+
*/
66+
public Map<Schema.SObjectField, Object> getFieldDefaults() {
67+
return new Map<Schema.SObjectField, Object>{
68+
Case.Subject => 'Test Case'
69+
};
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)