├── LICENSE ├── README.md └── src └── classes ├── IFieldProvider.cls ├── IFieldProvider.cls-meta.xml ├── RequiredFieldsCache.cls ├── RequiredFieldsCache.cls-meta.xml ├── SObjectBuilder.cls ├── SObjectBuilder.cls-meta.xml ├── SObjectFactory.cls ├── SObjectFactory.cls-meta.xml ├── SObjectFieldProviders.cls └── SObjectFieldProviders.cls-meta.xml /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above 15 | copyright holders shall not be used in advertising or otherwise to 16 | promote the sale, use or other dealings in this Software without 17 | prior written authorization. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 23 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 24 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SObjectFactory 2 | ============== 3 | The idea behind this utility is to generate records that can be inserted into the database of any `SObjectType` that is itself createable. Test authors should be able to specify **only data they care about**, freed from the concerns of which fields are required. A key paradigm this utility adopts is that `build` will generate record(s) but not perform any `DML`, while `create` will generate the same record(s) and insert them. This paradigm is consistent accross both the `SObjectFactory` and `SObjectBuilder` classes. 4 | 5 | The `SObjectFactory` class is the underlying engine to accomplish the above. The core functionality resides in the final `build` method, which accepts the `sObjectType` to be created, count of records, and any additional fields. This method merges the provided field-value pairs with any in the `RequiredFieldsCache` (provided fields win), then generates a list containing the desired number and type of records with these fields filled in. 6 | 7 | Methods in this class are best suited for creating simple record(s) where none of their data matters, or where only one field has data that is material to the test. For example: 8 | 9 | Id someUserId = SObjectFactory.create(User.sObjectType).Id; 10 | Lead someLead = (Lead)SObjectFactory.create(Lead.sObjectType, Lead.Email, 'jdoe@example.com'); 11 | 12 | RequiredFieldsCache 13 | =================== 14 | Much of the magic in this utility lies in the RequiredFieldsCache class, which maintains a repository of all fields that must be populated, and how. I have often been asked why this step is not approached programmatically (i.e. via describes), and the answer is that the ways in which a field becomes required are more complex than such an approach can solve. Consider simply a validation rule that requires Opportunity Close Date to be at least one month in the future. Try to determine a valid value for that field programatically, let alone even that it is required. Trigger validations can get far more complex and difficult to analyze than the above example. 15 | 16 | The only class in this utility that should need to change is the `RequiredFieldsCache`, except when a new `IFieldProvider` must be added. When you have a new object you want to be able to create with `SObjectFactory`, you simply need to populate its required fields into the cache. For example, if you added a custom setting named `My_Setting__c`: 17 | 18 | Map> cache = new Map> 19 | { 20 | My_Setting__c.sObjectType => new Map 21 | { 22 | My_Setting__c.Name => SObjectFactory.provideUniqueString('Setting') 23 | } 24 | } 25 | 26 | SObjectBuilder 27 | ============== 28 | When providing data for multiple fields, `SObjectFactory` can be fairly unwieldy. This is where `SObjectBuilder` comes in, providing syntactic sugar. Whereas the factory methods are all static, the builder methods are all instance. All methods in the builder can be chained, until calling either `getRecord` to return a single record, or `getRecords` to return a list thereof. It also has helper methods to create records as a System Administrator or mock insert by providing dummy ids. 29 | 30 | final Integer RECORD_COUNT = Limits.getLimitQueries() + 1; 31 | List opportunities = new SObjectBuilder(Opportunity.sObjectType) 32 | .put(Opportunity.AccountId, SObjectFactory.provideGenericParent(Account.sObjectType)) 33 | .put(Opportunity.CloseDate, SObjectFactory.provideUniqueDate()) 34 | .provideDummyIds().count(RECORD_COUNT).build().getRecords(); 35 | 36 | final String CUSTOM_SETTING_NAME = 'Some name'; 37 | new SObjectBuilder(My_Setting__c.sObjectType) 38 | .put(My_Setting__c.Name, CUSTOM_SETTING_NAME) 39 | .createAsAdmin(); 40 | 41 | SObjectFieldProviders 42 | ===================== 43 | Values provided to the factory do not have to be static, or even known at compile time. There are a variety of providers that allow for deferred assignment, which is especially useful when trying to create or query for related records in a limits-conscious way. These allow, for instance, to request a generic parent for a required lookup field that will not be inserted until needed. This deferred assignment is also critical in enabling chained generic parent records, for instance a `Lead` that looks up to an `Opportunity` that looks up to an `Account` that looks up to a `User`. The easiest way to access these, in general, is through the `SObjectFactory.provide` methods. 44 | 45 | SObjectFieldProviders.GenericParentProvider parent = SObjectFactory.provideGenericParent( 46 | Account.sObjectType, Account.OwnerId, SObjectFactory.provideGenericParent(User.sObjectType) 47 | ); 48 | SObjectFieldProviders.UniqueStringProviders uniqueString = SObjectFactory.provideUniqueString(); 49 | SObjectFieldProviders.MultiParentProvider parents = SObjectFactory.provideParents(Contact.sObjectType, 25); 50 | -------------------------------------------------------------------------------- /src/classes/IFieldProvider.cls: -------------------------------------------------------------------------------- 1 | public interface IFieldProvider { Object getValue(); } -------------------------------------------------------------------------------- /src/classes/IFieldProvider.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/RequiredFieldsCache.cls: -------------------------------------------------------------------------------- 1 | @IsTest 2 | public class RequiredFieldsCache 3 | { 4 | public static Map get(SObjectType sObjectType) 5 | { 6 | return cache.containsKey(sObjectType) ? 7 | cache.get(sObjectType) : new Map(); 8 | } 9 | 10 | public static IFieldProvider getGenericParentProvider(SObjectType sObjectType) 11 | { 12 | if (!genericParentProviders.containsKey(sObjectType)) 13 | genericParentProviders.put(sObjectType, SObjectFactory.provideGenericParent(sObjectType)); 14 | return genericParentProviders.get(sObjectType); 15 | } 16 | static Map genericParentProviders = new Map(); 17 | 18 | static Map> cache = 19 | new Map> 20 | { 21 | Account.sObjectType => new Map 22 | { 23 | Account.Name => 'Some Account' 24 | }, 25 | AccountContactRole.sObjectType => new Map 26 | { 27 | AccountContactRole.AccountId => getGenericParentProvider(Account.sObjectType), 28 | AccountContactRole.ContactId => getGenericParentProvider(Contact.sObjectType) 29 | }, 30 | AccountShare.sObjectType => new Map 31 | { 32 | AccountShare.AccountAccessLevel => 'Edit', 33 | AccountShare.OpportunityAccessLevel => 'Edit', 34 | AccountShare.AccountId => getGenericParentProvider(Account.sObjectType), 35 | AccountShare.UserOrGroupId => getGenericParentProvider(Group.sObjectType) 36 | }, 37 | AccountTeamMember.sObjectType => new Map 38 | { 39 | AccountTeamMember.AccountId => getGenericParentProvider(Account.sObjectType), 40 | AccountTeamMember.UserId => getGenericParentProvider(User.sObjectType) 41 | }, 42 | Announcement.sObjectType => new Map 43 | { 44 | Announcement.FeedItemId => SObjectFactory.provideGenericParent(FeedItem.sObjectType, FeedItem.ParentId, getGenericParentProvider(CollaborationGroup.sObjectType)), 45 | Announcement.ExpirationDate => Date.today().addDays(1) 46 | }, 47 | Asset.sObjectType => new Map 48 | { 49 | Asset.AccountId => getGenericParentProvider(Account.sObjectType), 50 | Asset.Name => 'Some Asset' 51 | }, 52 | Attachment.sObjectType => new Map 53 | { 54 | Attachment.ParentId => getGenericParentProvider(Account.sObjectType), 55 | Attachment.Body => Blob.valueOf('body'), 56 | Attachment.Name => 'Some Attachment' 57 | }, 58 | Campaign.sObjectType => new Map 59 | { 60 | Campaign.Name => 'Some Campaign' 61 | }, 62 | CampaignMember.sObjectType => new Map 63 | { 64 | CampaignMember.CampaignId => getGenericParentProvider(Campaign.sObjectType), 65 | CampaignMember.ContactId => getGenericParentProvider(Contact.sObjectType) 66 | }, 67 | CaseArticle.sObjectType => new Map 68 | { 69 | CaseArticle.CaseId => getGenericParentProvider(Case.sObjectType), 70 | CaseArticle.KnowledgeArticleId => SObjectFactory.provideQueriedParent(KnowledgeArticle.sObjectType) 71 | }, 72 | CaseComment.sObjectType => new Map 73 | { 74 | CaseComment.ParentId => getGenericParentProvider(Case.sObjectType) 75 | }, 76 | CaseContactRole.sObjectType => new Map 77 | { 78 | CaseContactRole.CasesId => getGenericParentProvider(Case.sObjectType), 79 | CaseContactRole.ContactId => getGenericParentProvider(Contact.sObjectType), 80 | CaseContactRole.Role => 'Role' 81 | }, 82 | CaseShare.sObjectType => new Map 83 | { 84 | CaseShare.CaseId => getGenericParentProvider(Case.sObjectType), 85 | CaseShare.UserOrGroupId => getGenericParentProvider(Group.sObjectType), 86 | CaseShare.CaseAccessLevel => 'Edit' 87 | }, 88 | CaseSolution.sObjectType => new Map 89 | { 90 | CaseSolution.CaseId => getGenericParentProvider(Case.sObjectType), 91 | CaseSolution.SolutionId => getGenericParentProvider(Solution.sObjectType) 92 | }, 93 | CaseTeamMember.sObjectType => new Map 94 | { 95 | CaseTeamMember.TeamRoleId => getGenericParentProvider(CaseTeamRole.sObjectType), 96 | CaseTeamMember.ParentId => getGenericParentProvider(Case.sObjectType), 97 | CaseTeamMember.MemberId => getGenericParentProvider(User.sObjectType) 98 | }, 99 | CaseTeamRole.sObjectType => new Map 100 | { 101 | CaseTeamRole.Name => SObjectFactory.provideUniqueString('Some Role'), 102 | CaseTeamRole.AccessLevel => 'Edit' 103 | }, 104 | CaseTeamTemplate.sObjectType => new Map 105 | { 106 | CaseTeamTemplate.Name => SObjectFactory.provideUniqueString('Some Template') 107 | }, 108 | CaseTeamTemplateMember.sObjectType => new Map 109 | { 110 | CaseTeamTemplateMember.TeamTemplateId => getGenericParentProvider(CaseTeamTemplate.sObjectType), 111 | CaseTeamTemplateMember.TeamRoleId => getGenericParentProvider(CaseTeamRole.sObjectType), 112 | CaseTeamTemplateMember.MemberId => getGenericParentProvider(User.sObjectType) 113 | }, 114 | CaseTeamTemplateRecord.sObjectType => new Map 115 | { 116 | CaseTeamTemplateRecord.TeamTemplateId => getGenericParentProvider(CaseTeamTemplate.sObjectType), 117 | CaseTeamTemplateRecord.ParentId => getGenericParentProvider(Case.sObjectType) 118 | }, 119 | CollaborationGroup.sObjectType => new Map 120 | { 121 | CollaborationGroup.Name => SObjectFactory.provideUniqueString('Chatter Group'), 122 | CollaborationGroup.CollaborationType => 'Public' 123 | }, 124 | CollaborationGroupMember.sObjectType => new Map 125 | { 126 | CollaborationGroupMember.CollaborationGroupId => getGenericParentProvider(CollaborationGroup.sObjectType), 127 | CollaborationGroupMember.MemberId => getGenericParentProvider(User.sObjectType) 128 | }, 129 | CollaborationGroupRecord.sObjectType => new Map 130 | { 131 | CollaborationGroupRecord.CollaborationGroupId => getGenericParentProvider(CollaborationGroup.sObjectType), 132 | CollaborationGroupRecord.RecordId => getGenericParentProvider(Account.sObjectType) 133 | }, 134 | CollaborationGroupMemberRequest.sObjectType => new Map 135 | { 136 | CollaborationGroupMemberRequest.CollaborationGroupId => getGenericParentProvider(CollaborationGroup.sObjectType), 137 | CollaborationGroupMemberRequest.RequesterId => UserInfo.getUserId() 138 | }, 139 | CollaborationInvitation.sObjectType => new Map 140 | { 141 | CollaborationInvitation.SharedEntityId => getGenericParentProvider(CollaborationGroup.sObjectType), 142 | CollaborationInvitation.InvitedUserEmail => 'jdoe@counsyl.com' 143 | }, 144 | Contact.sObjectType => new Map 145 | { 146 | Contact.LastName => 'Doe' 147 | }, 148 | ContentVersion.sObjectType => new Map 149 | { 150 | ContentVersion.Title => 'Some Content', 151 | ContentVersion.PathOnClient => 'some/path', 152 | ContentVersion.VersionData => Blob.valueOf('data') 153 | }, 154 | Contract.sObjectType => new Map 155 | { 156 | Contract.AccountId => getGenericParentProvider(Account.sObjectType) 157 | }, 158 | ContractContactRole.sObjectType => new Map 159 | { 160 | ContractContactRole.ContractId => getGenericParentProvider(Contract.sObjectType), 161 | ContractContactRole.ContactId => getGenericParentProvider(Contact.sObjectType) 162 | }, 163 | ContractLineItem.sObjectType => new Map 164 | { 165 | ContractLineItem.ServiceContractId => getGenericParentProvider(ServiceContract.sObjectType), 166 | ContractLineItem.PricebookEntryId => getGenericParentProvider(PricebookEntry.sObjectType), 167 | ContractLineItem.Quantity => 1, 168 | ContractLineItem.UnitPrice => 1 169 | }, 170 | Document.sObjectType => new Map 171 | { 172 | Document.FolderId => getGenericParentProvider(User.sObjectType), 173 | Document.Name => 'Some Document' 174 | }, 175 | EmailMessage.sObjectType => new Map 176 | { 177 | EmailMessage.ParentId => getGenericParentProvider(Case.sObjectType) 178 | }, 179 | EmailTemplate.sObjectType => new Map 180 | { 181 | EmailTemplate.DeveloperName => SObjectFactory.provideUniqueString('Template'), 182 | EmailTemplate.FolderId => getGenericParentProvider(User.sObjectType), 183 | EmailTemplate.TemplateType => 'Text', 184 | EmailTemplate.Name => 'Some Template' 185 | }, 186 | Entitlement.sObjectType => new Map 187 | { 188 | Entitlement.AccountId => getGenericParentProvider(Account.sObjectType), 189 | Entitlement.Name => 'Some Entitlement' 190 | }, 191 | EntitlementContact.sObjectType => new Map 192 | { 193 | EntitlementContact.EntitlementId => getGenericParentProvider(Entitlement.sObjectType), 194 | EntitlementContact.ContactId => getGenericParentProvider(Contact.sObjectType) 195 | }, 196 | EntitlementTemplate.sObjectType => new Map 197 | { 198 | EntitlementTemplate.Name => SObjectFactory.provideUniqueString('Template') 199 | }, 200 | EntitySubscription.sObjectType => new Map 201 | { 202 | EntitySubscription.ParentId => getGenericParentProvider(Account.sObjectType), 203 | EntitySubscription.SubscriberId => getGenericParentProvider(User.sObjectType) 204 | }, 205 | Event.sObjectType => new Map 206 | { 207 | Event.ActivityDateTime => Datetime.now(), 208 | Event.DurationInMinutes => 60 209 | }, 210 | EventRelation.sObjectType => new Map 211 | { 212 | EventRelation.EventId => getGenericParentProvider(Event.sObjectType), 213 | EventRelation.RelationId => getGenericParentProvider(User.sObjectType) 214 | }, 215 | FeedComment.sObjectType => new Map 216 | { 217 | FeedComment.FeedItemId => getGenericParentProvider(FeedItem.sObjectType), 218 | FeedComment.CommentBody => 'Some Comment' 219 | }, 220 | FeedItem.sObjectType => new Map 221 | { 222 | FeedItem.ParentId => getGenericParentProvider(Account.sObjectType), 223 | FeedItem.Body => 'Some Content' 224 | }, 225 | FeedLike.sObjectType => new Map 226 | { 227 | FeedLike.FeedItemId => getGenericParentProvider(FeedItem.sObjectType) 228 | }, 229 | SObjectType.FieldPermissions.getSObjectType() => new Map 230 | { 231 | FieldPermissions.Field => String.valueOf(Opportunity.sObjectType) + '.' + String.valueOf(Opportunity.Amount), 232 | FieldPermissions.ParentId => getGenericParentProvider(PermissionSet.sObjectType), 233 | FieldPermissions.SObjectType => String.valueOf(Opportunity.sObjectType), 234 | FieldPermissions.PermissionsRead => true 235 | }, 236 | GoogleDoc.sObjectType => new Map 237 | { 238 | GoogleDoc.Url => SObjectFactory.provideUniqueString('https://docs.google.com/a/example.com/file/d/'), 239 | GoogleDoc.ParentId => getGenericParentProvider(Account.sObjectType), 240 | GoogleDoc.Name => 'Some Document' 241 | }, 242 | Group.sObjectType => new Map 243 | { 244 | Group.Name => 'Some Group' 245 | }, 246 | GroupMember.sObjectType => new Map 247 | { 248 | GroupMember.GroupId => getGenericParentProvider(Group.sObjectType), 249 | GroupMember.UserOrGroupId => getGenericParentProvider(User.sObjectType) 250 | }, 251 | Holiday.sObjectType => new Map 252 | { 253 | Holiday.Name => 'Some Holiday', 254 | Holiday.ActivityDate => Date.today() 255 | }, 256 | Idea.sObjectType => new Map 257 | { 258 | Idea.CommunityId => SObjectFactory.provideQueriedParent(Community.sObjectType), 259 | Idea.Categories => 'Training', 260 | Idea.Title => 'Some Idea' 261 | }, 262 | IdeaComment.sObjectType => new Map 263 | { 264 | IdeaComment.IdeaId => getGenericParentProvider(Idea.sObjectType) 265 | }, 266 | Lead.sObjectType => new Map 267 | { 268 | Lead.LastName => 'Doe', 269 | Lead.Company => 'ACME' 270 | }, 271 | LeadShare.sObjectType => new Map 272 | { 273 | LeadShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 274 | LeadShare.LeadId => getGenericParentProvider(Lead.sObjectType), 275 | LeadShare.LeadAccessLevel => 'Edit' 276 | }, 277 | LiveAgentSession.sObjectType => new Map 278 | { 279 | LiveAgentSession.AgentId => getGenericParentProvider(User.sObjectType), 280 | LiveAgentSession.LoginTime => Datetime.now(), 281 | LiveAgentSession.LogoutTime => Datetime.now() 282 | }, 283 | LiveAgentSessionShare.sObjectType => new Map 284 | { 285 | LiveAgentSessionShare.ParentId => getGenericParentProvider(LiveAgentSession.sObjectType), 286 | LiveAgentSessionShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 287 | LiveAgentSessionShare.AccessLevel => 'Edit' 288 | }, 289 | LiveChatTranscript.sObjectType => new Map 290 | { 291 | LiveChatTranscript.LiveChatVisitorId => getGenericParentProvider(LiveChatVisitor.sObjectType) 292 | }, 293 | LiveChatTranscriptEvent.sObjectType => new Map 294 | { 295 | LiveChatTranscriptEvent.LiveChatTranscriptId => getGenericParentProvider(LiveChatTranscript.sObjectType), 296 | LiveChatTranscriptEvent.Time => Datetime.now(), 297 | LiveChatTranscriptEvent.Type => 'Transfer' 298 | }, 299 | LiveChatTranscriptShare.sObjectType => new Map 300 | { 301 | LiveChatTranscriptShare.ParentId => getGenericParentProvider(LiveChatTranscript.sObjectType), 302 | LiveChatTranscriptShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 303 | LiveChatTranscriptShare.AccessLevel => 'Edit' 304 | }, 305 | LiveChatTranscriptSkill.sObjectType => new Map 306 | { 307 | LiveChatTranscriptSkill.TranscriptId => getGenericParentProvider(LiveChatTranscript.sObjectType), 308 | LiveChatTranscriptSkill.SkillId => SObjectFactory.provideQueriedParent(Skill.sObjectType) 309 | }, 310 | MilestoneType.sObjectType => new Map 311 | { 312 | MilestoneType.Name => SObjectFactory.provideUniqueString('MilestoneType') 313 | }, 314 | NetworkModeration.sObjectType => new Map 315 | { 316 | NetworkModeration.NetworkId => SObjectFactory.provideQueriedParent(Community.sObjectType), 317 | NetworkModeration.EntityId => getGenericParentProvider(FeedItem.sObjectType) 318 | }, 319 | Note.sObjectType => new Map 320 | { 321 | Note.ParentId => getGenericParentProvider(Account.sObjectType), 322 | Note.Title => 'Some Note' 323 | }, 324 | SObjectType.ObjectPermissions.getSObjectType() => new Map 325 | { 326 | ObjectPermissions.ParentId => getGenericParentProvider(PermissionSet.sObjectType), 327 | ObjectPermissions.SObjectType => String.valueOf(Account.sObjectType), 328 | ObjectPermissions.PermissionsRead => true 329 | }, 330 | Opportunity.sObjectType => new Map 331 | { 332 | Opportunity.AccountId => getGenericParentProvider(Account.sObjectType), 333 | Opportunity.Name => 'Some Opportunity', 334 | Opportunity.StageName => 'Some Stage', 335 | Opportunity.CloseDate => Date.today() 336 | }, 337 | OpportunityCompetitor.sObjectType => new Map 338 | { 339 | OpportunityCompetitor.OpportunityId => getGenericParentProvider(Opportunity.sObjectType) 340 | }, 341 | OpportunityContactRole.sObjectType => new Map 342 | { 343 | OpportunityContactRole.OpportunityId => getGenericParentProvider(Opportunity.sObjectType), 344 | OpportunityContactRole.ContactId => getGenericParentProvider(Contact.sObjectType) 345 | }, 346 | OpportunityLineItem.sObjectType => new Map 347 | { 348 | OpportunityLineItem.OpportunityId => getGenericParentProvider(Opportunity.sObjectType), 349 | OpportunityLineItem.PricebookEntryId => getGenericParentProvider(PricebookEntry.sObjectType), 350 | OpportunityLineItem.UnitPrice => 1, 351 | OpportunityLineItem.Quantity => 1 352 | }, 353 | OpportunityShare.sObjectType => new Map 354 | { 355 | OpportunityShare.OpportunityId => getGenericParentProvider(Opportunity.sObjectType), 356 | OpportunityShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 357 | OpportunityShare.OpportunityAccessLevel => 'Edit' 358 | }, 359 | Partner.sObjectType => new Map 360 | { 361 | Partner.AccountFromId => getGenericParentProvider(Account.sObjectType), 362 | Partner.AccountToId => SObjectFactory.provideGenericParent(Account.sObjectType) 363 | }, 364 | PermissionSet.sObjectType => new Map 365 | { 366 | PermissionSet.Name => SObjectFactory.provideUniqueString('TestPermissions'), 367 | PermissionSet.Label => SObjectFactory.provideUniqueString('Test Permissions') 368 | }, 369 | PermissionSetAssignment.sObjectType => new Map 370 | { 371 | PermissionSetAssignment.PermissionSetId => getGenericParentProvider(PermissionSet.sObjectType), 372 | PermissionSetAssignment.AssigneeId => getGenericParentProvider(User.sObjectType) 373 | }, 374 | Pricebook2.sObjectType => new Map 375 | { 376 | Pricebook2.Name => 'Some Pricebook' 377 | }, 378 | PricebookEntry.sObjectType => new Map 379 | { 380 | PricebookEntry.Product2Id => getGenericParentProvider(Product2.sObjectType), 381 | PricebookEntry.Pricebook2Id => Test.getStandardPricebookId(), 382 | PricebookEntry.IsActive => true, 383 | PricebookEntry.UnitPrice => 1 384 | }, 385 | Product2.sObjectType => new Map 386 | { 387 | Product2.Name => 'Some Product' 388 | }, 389 | ProductEntitlementTemplate.sObjectType => new Map 390 | { 391 | ProductEntitlementTemplate.EntitlementTemplateId => getGenericParentProvider(EntitlementTemplate.sObjectType), 392 | ProductEntitlementTemplate.Product2Id => getGenericParentProvider(Product2.sObjectType) 393 | }, 394 | PushTopic.sObjectType => new Map 395 | { 396 | PushTopic.Name => SObjectFactory.provideUniqueString('PushTopic'), 397 | PushTopic.Query => 'SELECT Id FROM Account WHERE Name = null', 398 | PushTopic.ApiVersion => 35 399 | }, 400 | QuantityForecast.sObjectType => new Map 401 | { 402 | QuantityForecast.OwnerId => SObjectFactory.provideGenericParent(User.sObjectType, User.ForecastEnabled, true), 403 | QuantityForecast.StartDate => Date.today(), 404 | QuantityForecast.Quota => 10 405 | }, 406 | Question.sObjectType => new Map 407 | { 408 | Question.CommunityId => SObjectFactory.provideQueriedParent(Community.sObjectType), 409 | Question.Title => 'Some Question' 410 | }, 411 | QuestionDataCategorySelection.sObjectType => new Map 412 | { 413 | QuestionDataCategorySelection.ParentId => getGenericParentProvider(Question.sObjectType), 414 | QuestionDataCategorySelection.DataCategoryName => 'Privacy_and_Security', 415 | QuestionDataCategorySelection.DataCategoryGroupName => 'Support' 416 | }, 417 | SObjectType.QueueSobject.getSObjectType() => new Map 418 | { 419 | QueueSobject.QueueId => SObjectFactory.provideGenericParent(Group.sObjectType, Group.Type, 'Queue'), 420 | QueueSobject.SobjectType => String.valueOf(Case.sObjectType) 421 | }, 422 | QuickText.sObjectType => new Map 423 | { 424 | QuickText.Message => 'Some Message', 425 | QuickText.Name => 'Some Text' 426 | }, 427 | QuickTextShare.sObjectType => new Map 428 | { 429 | QuickTextShare.ParentId => getGenericParentProvider(QuickText.sObjectType), 430 | QuickTextShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 431 | QuickTextShare.AccessLevel => 'Edit' 432 | }, 433 | Reply.sObjectType => new Map 434 | { 435 | Reply.QuestionId => getGenericParentProvider(Question.sObjectType), 436 | Reply.Body => 'Some Body' 437 | }, 438 | RevenueForecast.sObjectType => new Map 439 | { 440 | RevenueForecast.OwnerId => SObjectFactory.provideGenericParent(User.sObjectType, User.ForecastEnabled, true), 441 | RevenueForecast.StartDate => Date.today(), 442 | RevenueForecast.Quota => 10 443 | }, 444 | SearchPromotionRule.sObjectType => new Map 445 | { 446 | SearchPromotionRule.Query => 'some query' 447 | }, 448 | ServiceContract.sObjectType => new Map 449 | { 450 | ServiceContract.Pricebook2Id => Test.getStandardPricebookId(), 451 | ServiceContract.Name => 'Some Contract' 452 | }, 453 | ServiceContractShare.sObjectType => new Map 454 | { 455 | ServiceContractShare.ParentId => getGenericParentProvider(ServiceContract.sObjectType), 456 | ServiceContractShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 457 | ServiceContractShare.AccessLevel => 'Edit' 458 | }, 459 | Solution.sObjectType => new Map 460 | { 461 | Solution.SolutionName => 'Some Solution' 462 | }, 463 | SetupEntityAccess.sObjectType => new Map 464 | { 465 | SetupEntityAccess.ParentId => getGenericParentProvider(PermissionSet.sObjectType), 466 | SetupEntityAccess.SetupEntityId => SObjectFactory.provideQueriedParent( 467 | 'SELECT Id FROM ApexClass WHERE NamespacePrefix = null LIMIT 1' 468 | ) 469 | }, 470 | SocialPersona.sObjectType => new Map 471 | { 472 | SocialPersona.ParentId => getGenericParentProvider(Account.sObjectType), 473 | SocialPersona.Provider => 'Twitter', 474 | SocialPersona.Name => 'Somebody' 475 | }, 476 | SocialPost.sObjectType => new Map 477 | { 478 | SocialPost.Name => 'Some Post' 479 | }, 480 | SocialPostShare.sObjectType => new Map 481 | { 482 | SocialPostShare.ParentId => getGenericParentProvider(SocialPost.sObjectType), 483 | SocialPostShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 484 | SocialPostShare.AccessLevel => 'Edit' 485 | }, 486 | StreamingChannel.sObjectType => new Map 487 | { 488 | StreamingChannel.Name => SObjectFactory.provideUniqueString('/u/TestChannel') 489 | }, 490 | StreamingChannelShare.sObjectType => new Map 491 | { 492 | StreamingChannelShare.ParentId => getGenericParentProvider(StreamingChannel.sObjectType), 493 | StreamingChannelShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 494 | StreamingChannelShare.AccessLevel => 'Edit' 495 | }, 496 | Topic.sObjectType => new Map 497 | { 498 | Topic.Name => SObjectFactory.provideUniqueString('Topic') 499 | }, 500 | TopicAssignment.sObjectType => new Map 501 | { 502 | TopicAssignment.EntityId => getGenericParentProvider(FeedItem.sObjectType), 503 | TopicAssignment.TopicId => getGenericParentProvider(Topic.sObjectType) 504 | }, 505 | User.sObjectType => new Map 506 | { 507 | User.LastName => 'Doe', 508 | User.LocaleSidKey => 'en_US', 509 | User.Email => 'jdoe@example.com', 510 | User.LanguageLocaleKey => 'en_US', 511 | User.EmailEncodingKey => 'ISO-8859-1', 512 | User.TimeZoneSidKey => 'America/Denver', 513 | User.ProfileId => SObjectFactory.provideStandardProfile(), 514 | User.Alias => SObjectFactory.provideUniqueFixedLengthString('jdoe', 7), 515 | User.CommunityNickname => SObjectFactory.provideUniqueString('johndoe'), 516 | User.Username => SObjectFactory.provideUniqueString('1234567890abcxyz@example.com') 517 | }, 518 | UserPackageLicense.sObjectType => new Map 519 | { 520 | UserPackageLicense.UserId => getGenericParentProvider(User.sObjectType), 521 | UserPackageLicense.PackageLicenseId => SObjectFactory.provideQueriedParent(PackageLicense.sObjectType) 522 | }, 523 | UserProvisioningRequest.sObjectType => new Map 524 | { 525 | UserProvisioningRequest.State => 'New', 526 | UserProvisioningRequest.Operation => 'Read', 527 | UserProvisioningRequest.ApprovalStatus => 'Denied' 528 | }, 529 | UserProvisioningRequestShare.sObjectType => new Map 530 | { 531 | UserProvisioningRequestShare.ParentId => getGenericParentProvider(UserProvisioningRequest.sObjectType), 532 | UserProvisioningRequestShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 533 | UserProvisioningRequestShare.AccessLevel => 'Edit' 534 | }, 535 | UserRole.sObjectType => new Map 536 | { 537 | UserRole.Name => 'Some Role' 538 | }, 539 | UserShare.sObjectType => new Map 540 | { 541 | UserShare.UserId => getGenericParentProvider(User.sObjectType), 542 | UserShare.UserOrGroupId => getGenericParentProvider(User.sObjectType), 543 | UserShare.UserAccessLevel => 'Edit' 544 | }, 545 | Vote.sObjectType => new Map 546 | { 547 | Vote.ParentId => getGenericParentProvider(Question.sObjectType), 548 | Vote.Type => 'Up' 549 | } 550 | }; 551 | } -------------------------------------------------------------------------------- /src/classes/RequiredFieldsCache.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/SObjectBuilder.cls: -------------------------------------------------------------------------------- 1 | @IsTest 2 | public class SObjectBuilder 3 | { 4 | final List records; 5 | final SObjectType sObjectType; 6 | final Map fieldToValue; 7 | 8 | Integer count; 9 | 10 | public SObjectBuilder(SObjectType sObjectType) 11 | { 12 | this.count = 1; 13 | this.sObjectType = sObjectType; 14 | this.records = new List(); 15 | this.fieldToValue = new Map(); 16 | } 17 | 18 | public SObjectBuilder count(Integer count) 19 | { 20 | this.count = count; 21 | return this; 22 | } 23 | 24 | public SObjectBuilder put(SObjectField field, Object value) 25 | { 26 | this.fieldToValue.put(field, value); 27 | return this; 28 | } 29 | public SObjectBuilder putAll(Map fieldToValue) 30 | { 31 | this.fieldToValue.putAll(fieldToValue); 32 | return this; 33 | } 34 | public SObjectBuilder provideDummyIds() 35 | { 36 | SObjectField idField = this.sObjectType.getDescribe().fields.getMap().get('id'); 37 | this.put(idField, SObjectFactory.provideDummyId(this.sObjectType)); 38 | return this; 39 | } 40 | 41 | public SObjectBuilder build() 42 | { 43 | this.records.clear(); 44 | this.records.addAll(SObjectFactory.build(sObjectType, count, fieldToValue)); 45 | return this; 46 | } 47 | public SObjectBuilder create() 48 | { 49 | this.records.clear(); 50 | this.records.addAll(SObjectFactory.create(sObjectType, count, fieldToValue)); 51 | return this; 52 | } 53 | public SObjectBuilder createAsAdmin() 54 | { 55 | system.runAs(SObjectFactory.ADMIN_USER) { this.create(); } 56 | return this; 57 | } 58 | 59 | public SObject getRecord() { return this.records[0]; } 60 | public List getRecords() { return this.records; } 61 | } -------------------------------------------------------------------------------- /src/classes/SObjectBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/SObjectFactory.cls: -------------------------------------------------------------------------------- 1 | @IsTest 2 | public class SObjectFactory 3 | { 4 | public static User ADMIN_USER 5 | { 6 | get 7 | { 8 | if (ADMIN_USER == null) 9 | ADMIN_USER = [ 10 | SELECT Id FROM User WHERE IsActive = true 11 | AND Profile.Name = 'Counsyl System Admin' 12 | LIMIT 1 13 | ]; 14 | return ADMIN_USER; 15 | } 16 | private set; 17 | } 18 | public static Boolean isProduction 19 | { 20 | get 21 | { 22 | if (isProduction == null) 23 | isProduction = ![SELECT IsSandbox FROM Organization].IsSandbox; 24 | return isProduction; 25 | } 26 | private set; 27 | } 28 | 29 | public static Integer getRecordThreshold() 30 | { 31 | return isProduction ? 5 : Limits.getLimitQueries() + 1; 32 | } 33 | 34 | // no fields 35 | public static SObject build(SObjectType sObjectType) { return build(sObjectType, 1)[0]; } 36 | public static SObject create(SObjectType sObjectType) { return create(sObjectType, 1)[0]; } 37 | 38 | public static List build(SObjectType sObjectType, Integer count) 39 | { 40 | return build(sObjectType, count, /*fieldToValue*/ null); 41 | } 42 | public static List create(SObjectType sObjectType, Integer count) 43 | { 44 | return create(sObjectType, count, /*fieldToValue*/ null); 45 | } 46 | 47 | // one field 48 | public static SObject build(SObjectType sObjectType, SObjectField field, Object value) 49 | { 50 | return build(sObjectType, 1, field, value)[0]; 51 | } 52 | public static SObject create(SObjectType sObjectType, SObjectField field, Object value) 53 | { 54 | return create(sObjectType, 1, field, value)[0]; 55 | } 56 | 57 | public static List build(SObjectType sObjectType, Integer count, SObjectField field, Object value) 58 | { 59 | return build(sObjectType, count, new Map { field => value }); 60 | } 61 | public static List create(SObjectType sObjectType, Integer count, SObjectField field, Object value) 62 | { 63 | return create(sObjectType, count, new Map { field => value }); 64 | } 65 | 66 | // all fields 67 | public static List build( 68 | SObjectType sObjectType, Integer count, 69 | Map fieldToValue 70 | ) 71 | { 72 | Map fields = RequiredFieldsCache.get(sObjectType); 73 | if (fieldToValue != null) fields.putAll(fieldToValue); 74 | 75 | List records = new List(); 76 | while (records.size() < count) 77 | { 78 | SObject record = sObjectType.newSObject(); 79 | for (SObjectField field : fields.keySet()) 80 | { 81 | Object value = fields.get(field); 82 | if (value instanceOf IFieldProvider) 83 | value = ((IFieldProvider)value).getValue(); 84 | record.put(field, value); 85 | } 86 | records.add(record); 87 | } 88 | return records; 89 | } 90 | public static List create( 91 | SObjectType sObjectType, Integer count, 92 | Map fieldToValue 93 | ) 94 | { 95 | List records = build(sObjectType, count, fieldToValue); 96 | insert records; 97 | return records; 98 | } 99 | 100 | // providers 101 | public static IFieldProvider provideStandardProfile() 102 | { 103 | return new SObjectFieldProviders.QueryParentProvider( 104 | 'SELECT Id FROM Profile WHERE Name = \'Standard User\' LIMIT 1' 105 | ); 106 | } 107 | 108 | public static IFieldProvider provideUniqueNumber() 109 | { 110 | return new SObjectFieldProviders.UniqueNumberProvider(); 111 | } 112 | public static IFieldProvider provideUniqueNumber(Integer start) 113 | { 114 | return new SObjectFieldProviders.UniqueNumberProvider(start); 115 | } 116 | 117 | public static IFieldProvider provideUniqueDate() 118 | { 119 | return new SObjectFieldProviders.UniqueDateProvider(); 120 | } 121 | public static IFieldProvider provideUniqueDate(Date startDate) 122 | { 123 | return new SObjectFieldProviders.UniqueDateProvider(startDate); 124 | } 125 | 126 | public static IFieldProvider provideUniqueString() 127 | { 128 | return new SObjectFieldProviders.UniqueStringProvider(); 129 | } 130 | public static IFieldProvider provideUniqueString(String base) 131 | { 132 | return new SObjectFieldProviders.UniqueStringProvider(base); 133 | } 134 | 135 | public static IFieldProvider provideUniqueFixedLengthString(Integer length) 136 | { 137 | return new SObjectFieldProviders.UniqueFixedLengthStringProvider(length); 138 | } 139 | public static IFieldProvider provideUniqueFixedLengthString(String base, Integer length) 140 | { 141 | return new SObjectFieldProviders.UniqueFixedLengthStringProvider(base, length); 142 | } 143 | 144 | public static IFieldProvider provideDummyId(SObjectType sObjectType) 145 | { 146 | return new SObjectFieldProviders.DummyIdProvider(sObjectType); 147 | } 148 | 149 | public static IFieldProvider provideGenericParent(SObjectType sObjectType) 150 | { 151 | return new SObjectFieldProviders.GenericParentProvider(sObjectType); 152 | } 153 | public static IFieldProvider provideGenericParent(SObjectType sObjectType, SObjectField field, Object value) 154 | { 155 | return new SObjectFieldProviders.GenericParentProvider(sObjectType, field, value); 156 | } 157 | 158 | public static IFieldProvider provideList(List values) 159 | { 160 | return new SObjectFieldProviders.ListProvider(values); 161 | } 162 | 163 | public static IFieldProvider provideParents(SObjectType sObjectType, Integer count) 164 | { 165 | return new SObjectFieldProviders.MultiParentProvider(SObjectFactory.create(sObjectType, count)); 166 | } 167 | public static IFieldProvider provideParents(List parents) 168 | { 169 | return new SObjectFieldProviders.MultiParentProvider(parents); 170 | } 171 | public static IFieldProvider provideParents(Set parentIds) 172 | { 173 | return new SObjectFieldProviders.MultiParentProvider(parentIds); 174 | } 175 | 176 | public static IFieldProvider provideQueriedParent(String query) 177 | { 178 | return new SObjectFieldProviders.QueryParentProvider(query); 179 | } 180 | public static IFieldProvider provideQueriedParent(SObjectType sObjectType) 181 | { 182 | return new SObjectFieldProviders.QueryParentProvider(sObjectType); 183 | } 184 | } -------------------------------------------------------------------------------- /src/classes/SObjectFactory.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/SObjectFieldProviders.cls: -------------------------------------------------------------------------------- 1 | @IsTest 2 | public class SObjectFieldProviders 3 | { 4 | public class CountException extends Exception { } 5 | public class EmptyQueryException extends Exception { } 6 | 7 | public class UniqueNumberProvider implements IFieldProvider 8 | { 9 | Integer counter; 10 | public UniqueNumberProvider() { this(0); } 11 | public UniqueNumberProvider(Integer start) 12 | { 13 | this.counter = start; 14 | } 15 | public Object getValue() 16 | { 17 | return counter++; 18 | } 19 | } 20 | 21 | public class UniqueDateProvider implements IFieldProvider 22 | { 23 | final Date startDate; 24 | Integer counter; 25 | public UniqueDateProvider() { this(Date.today()); } 26 | public UniqueDateProvider(Date startDate) 27 | { 28 | this.counter = 0; 29 | this.startDate = startDate; 30 | } 31 | public Object getValue() 32 | { 33 | return startDate.addMonths(counter++); 34 | } 35 | } 36 | 37 | public class UniqueStringProvider implements IFieldProvider 38 | { 39 | final String base; 40 | Integer counter; 41 | public UniqueStringProvider() { this(''); } 42 | public UniqueStringProvider(String base) 43 | { 44 | this.base = base; 45 | this.counter = 0; 46 | } 47 | public Object getValue() 48 | { 49 | return base + counter++; 50 | } 51 | } 52 | 53 | public virtual class UniqueFixedLengthStringProvider implements IFieldProvider 54 | { 55 | final String base; 56 | final Integer remainingLength; 57 | Integer counter; 58 | 59 | public UniqueFixedLengthStringProvider(Integer length) { this('', length); } 60 | public UniqueFixedLengthStringProvider(String base, Integer length) 61 | { 62 | this.base = base; 63 | this.remainingLength = length - base.length(); 64 | this.counter = 0; 65 | } 66 | public Object getValue() 67 | { 68 | String counterValue = String.valueOf(counter++); 69 | Integer counterLength = counterValue.length(); 70 | if (counterLength < remainingLength) 71 | counterValue = counterValue.leftPad(remainingLength).replace(' ', '0'); 72 | else if (counterLength > remainingLength) 73 | throw new CountException('Too many fixed length strings'); 74 | return base + counterValue; 75 | } 76 | } 77 | public class DummyIdProvider 78 | extends UniqueFixedLengthStringProvider implements IFieldProvider 79 | { 80 | public DummyIdProvider(String prefix, Integer length) { super(prefix, length); } 81 | public DummyIdProvider(SObjectType sObjectType) { this(sObjectType, 18); } 82 | public DummyIdProvider(SObjectType sObjectType, Integer length) 83 | { 84 | this(sObjectType.getDescribe().getKeyPrefix(), length); 85 | } 86 | } 87 | 88 | public virtual class ListProvider implements IFieldProvider 89 | { 90 | final Integer size; 91 | final List values; 92 | Integer index; 93 | public ListProvider(List values) 94 | { 95 | this.values = values; 96 | this.size = values.size(); 97 | this.index = 0; 98 | } 99 | public Object getValue() 100 | { 101 | Object value = values[index]; 102 | index = Math.mod(index + 1, size); 103 | return value; 104 | } 105 | } 106 | public class MultiParentProvider extends ListProvider implements IFieldProvider 107 | { 108 | public MultiParentProvider(Set parentIds) 109 | { 110 | super(new List(parentIds)); 111 | } 112 | public MultiParentProvider(List parents) 113 | { 114 | this(pluckIds(parents)); 115 | } 116 | } 117 | 118 | public class GenericParentProvider implements IFieldProvider 119 | { 120 | final SObjectBuilder builder; 121 | Id genericParentId; 122 | public GenericParentProvider(SObjectType sObjectType) 123 | { 124 | this.builder = new SObjectBuilder(sObjectType); 125 | } 126 | public GenericParentProvider(SObjectType sObjectType, SObjectField field, Object value) 127 | { 128 | this.builder = new SObjectBuilder(sObjectType).put(field, value); 129 | } 130 | public Object getValue() 131 | { 132 | if (genericParentId == null) 133 | genericParentId = builder.create().getRecord().Id; 134 | return genericParentId; 135 | } 136 | } 137 | 138 | public class QueryParentProvider implements IFieldProvider 139 | { 140 | final String query; 141 | Id queriedParentId; 142 | public QueryParentProvider(String query) 143 | { 144 | this.query = query; 145 | } 146 | public QueryParentProvider(SObjectType sObjectType) 147 | { 148 | this.query = 'SELECT Id FROM ' + String.valueOf(sObjectType) + ' LIMIT 1'; 149 | } 150 | public Object getValue() 151 | { 152 | if (queriedParentId == null) 153 | { 154 | List candidates = Database.query(query); 155 | if (candidates.isEmpty()) throw new EmptyQueryException(); 156 | queriedParentId = candidates[0].Id; 157 | } 158 | return queriedParentId; 159 | } 160 | } 161 | 162 | static Set pluckIds(List records) 163 | { 164 | Map recordMap = new Map(); 165 | recordMap.putAll(records); 166 | return recordMap.keySet(); 167 | } 168 | } -------------------------------------------------------------------------------- /src/classes/SObjectFieldProviders.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35.0 4 | Active 5 | 6 | --------------------------------------------------------------------------------