From a79e869910f1701b383126b91095f36c6535bd05 Mon Sep 17 00:00:00 2001 From: Piotr PG Gajek Date: Mon, 2 Feb 2026 22:11:30 +0100 Subject: [PATCH 1/2] Randomizers --- force-app/main/default/classes/TestModule.cls | 216 ++++++++++-------- .../main/default/classes/TestModule_Test.cls | 155 ++++--------- .../concrete-modules/AccountTestModule.cls | 20 +- .../concrete-modules/ContactTestModule.cls | 63 +++++ .../OpportunityTestModule.cls | 60 +++++ website/api.md | 16 +- website/best-practices.md | 2 +- website/builder.md | 10 +- website/examples.md | 2 +- website/mocker.md | 2 +- website/randomizers.md | 34 +-- 11 files changed, 333 insertions(+), 247 deletions(-) diff --git a/force-app/main/default/classes/TestModule.cls b/force-app/main/default/classes/TestModule.cls index 8792e9a..9a9ec15 100644 --- a/force-app/main/default/classes/TestModule.cls +++ b/force-app/main/default/classes/TestModule.cls @@ -1,15 +1,6 @@ @IsTest @TestVisible private class TestModule { - public class TestModuleException extends Exception {} - - private static Integer fakeIdCounter = 1; - - public static Id fakeId(SObjectType sot) { - String keyPrefix = sot.getDescribe().getKeyPrefix(); - return Id.valueOf(keyPrefix + '0'.repeat(12 - String.valueOf(fakeIdCounter).length()) + fakeIdCounter++); - } - public interface BuilderProvider { Builder Builder(); } @@ -22,8 +13,8 @@ private class TestModule { Builder set(SObjectField field, Object value); Builder set(String field, Object value); Builder useTemplate(String templateName); - Builder withRandomizer(Randomizer randomizer); - Builder withRandomizer(SObjectField field, SingleFieldRandomizer randomizer); + Builder withRandomizer(TestModule.RecordRandomizer randomizer); + Builder withRandomizer(SObjectField field, TestModule.FieldRandomizer randomizer); SObject build(); SObject buildAndInsert(); @@ -33,11 +24,11 @@ private class TestModule { public interface Mocker { Mocker set(SObjectField field, Object value); - Mocker set(String field, Object value); + Mocker set(String field, Object value); // parent relationships, formulas, rollup sums, etc. Mocker setChildren(String relationship, List children); Mocker setFakeId(); - Mocker withRandomizer(Randomizer randomizer); - Mocker withRandomizer(SObjectField field, SingleFieldRandomizer randomizer); + Mocker withRandomizer(TestModule.RecordRandomizer randomizer); + Mocker withRandomizer(SObjectField field, TestModule.FieldRandomizer randomizer); SObject build(); List build(Integer amount); @@ -48,55 +39,30 @@ private class TestModule { Map templates(); } - public interface SingleFieldRandomizer { + public interface FieldRandomizer { Object generate(Integer index); } - public interface Randomizer { - Map generate(Integer index); + public interface RecordRandomizer { + Map randomizers(); } - public virtual class RecordRandomizer implements Randomizer { - private Randomizer parent; - private Map fieldRandomizers = new Map(); + // Default utilities - public RecordRandomizer setParent(Randomizer parent) { - this.parent = parent; - return this; - } + public static final RandomIdGenerator IdGenerator = new RandomIdGenerator(); - public RecordRandomizer add(SObjectField field, SingleFieldRandomizer randomizer) { - this.fieldRandomizers.put(field, randomizer); - return this; - } - - public Map generate(Integer index) { - Map values = this.parent != null - ? this.parent.generate(index) - : new Map(); - for (SObjectField field : this.fieldRandomizers.keySet()) { - values.put(field, this.fieldRandomizers.get(field).generate(index)); - } - return values; - } + public static Id fakeId(SObjectType objectType) { + return IdGenerator.get(objectType); } - public class ListRandomizer implements SingleFieldRandomizer { - private List values; - - public ListRandomizer(List values) { - this.values = values; - } - - public Object generate(Integer index) { - return values[Math.mod(index, values.size())]; - } + public static FieldRandomizer ListRandomizer(List values) { + return new ListRandomizer(values); } public abstract class RecordBuilder implements Builder { private SObject prototype; private Template templates; - private RecordRandomizer randomizer = new RecordRandomizer(); + private RandomizersContainer randomizers = new RandomizersContainer(); public RecordBuilder(SObject prototype) { this.prototype = prototype; @@ -135,22 +101,18 @@ private class TestModule { return this; } - public virtual RecordBuilder withRandomizer(Randomizer randomizer) { - this.randomizer.setParent(randomizer); - Map values = randomizer.generate(0); - for (SObjectField field : values.keySet()) { - this.prototype.put(field, values.get(field)); - } + public virtual RecordBuilder withRandomizer(RecordRandomizer randomizer) { + this.randomizers.add(randomizer); return this; } - public virtual RecordBuilder withRandomizer(SObjectField field, SingleFieldRandomizer randomizer) { - this.randomizer.add(field, randomizer); - this.prototype.put(field, randomizer.generate(0)); + public virtual RecordBuilder withRandomizer(SObjectField field, FieldRandomizer randomizer) { + this.randomizers.add(field, randomizer); return this; } public SObject build() { + this.randomizers.randomizeFieldsValues(this.prototype, 0); return this.prototype; } @@ -161,29 +123,21 @@ private class TestModule { public List build(Integer amount) { List records = new List(); + this.randomizeFieldsValues(records, amount); + return records; + } + private void randomizeFieldsValues(List records, Integer amount) { for (Integer i = 0; i < amount; i++) { SObject cloned = this.prototype.clone(false, true, false, false); - - Map randomizerValues = this.randomizer.generate(i); - for (SObjectField field : randomizerValues.keySet()) { - Object value = randomizerValues.get(field); - if (value != null) { - cloned.put(field, value); - } - } - + this.randomizers.randomizeFieldsValues(cloned, i); records.add(cloned); } - - return records; } public List buildAndInsert(Integer amount) { List records = this.build(amount); - insert as system records; - return records; } } @@ -192,7 +146,7 @@ private class TestModule { private Map fields = new Map(); private Map> children = new Map>(); private Template templates; - private RecordRandomizer randomizer = new RecordRandomizer(); + private RandomizersContainer randomizers = new RandomizersContainer(); private SObjectType prototypeType; @@ -221,17 +175,17 @@ private class TestModule { } public virtual RecordMocker setFakeId() { - this.fields.put('Id', TestModule.fakeId(this.prototypeType)); + this.fields.put('Id', TestModule.IdGenerator.get(this.prototypeType)); return this; } - public virtual RecordMocker withRandomizer(Randomizer randomizer) { - this.randomizer.setParent(randomizer); + public virtual RecordMocker withRandomizer(SObjectField field, FieldRandomizer randomizer) { + this.randomizers.add(field, randomizer); return this; } - public virtual RecordMocker withRandomizer(SObjectField field, SingleFieldRandomizer randomizer) { - this.randomizer.add(field, randomizer); + public virtual RecordMocker withRandomizer(RecordRandomizer randomizer) { + this.randomizers.add(randomizer); return this; } @@ -251,9 +205,10 @@ private class TestModule { Map record = new Map(); this.setNested(record); - this.randomizeFieldsValues(record); this.setChildren(record); + this.randomizers.randomizeFieldsValues(record, index); + return (SObject) JSON.deserialize(JSON.serialize(record), Type.forName(this.prototypeType.toString())); } @@ -263,20 +218,6 @@ private class TestModule { } } - public void randomizeFieldsValues(Map record) { - Map randomizerValues = this.randomizer.generate(0); - - for (SObjectField field : randomizerValues.keySet()) { - Object value = randomizerValues.get(field); - - if (value == null) { - continue; - } - - record.put(field.toString(), value); - } - } - private void setNestedValue(Map record, String fieldPath, Object value) { List parts = fieldPath.split('\\.'); Map current = record; @@ -298,8 +239,97 @@ private class TestModule { for (String relationship : this.children.keySet()) { List childRecords = this.children.get(relationship); - record.put(relationship, new Map{ 'totalSize' => childRecords.size(), 'done' => true, 'records' => JSON.deserializeUntyped(JSON.serialize(childRecords)) }); + record.put( + relationship, + new Map{ 'totalSize' => childRecords.size(), 'done' => true, 'records' => JSON.deserializeUntyped(JSON.serialize(childRecords)) } + ); } } } + + // Default randomizers + + public class ListRandomizer implements FieldRandomizer { + private List values; + + public ListRandomizer(List values) { + this.values = values; + } + + public Object generate(Integer index) { + return values[Math.mod(index, values.size())]; + } + } + + private class RandomizersContainer { + private Map fieldRandomizers = new Map(); + + public RandomizersContainer add(SObjectField field, FieldRandomizer randomizer) { + this.fieldRandomizers.put(field, randomizer); + return this; + } + + public RandomizersContainer add(RecordRandomizer randomizer) { + for (SObjectField field : randomizer.randomizers().keySet()) { + this.add(field, randomizer.randomizers().get(field)); + } + + return this; + } + + public void randomizeFieldsValues(SObject record, Integer index) { + for (SObjectField field : this.fieldRandomizers.keySet()) { + Object value = this.fieldRandomizers.get(field).generate(index); + + if (value == null) { + continue; + } + + record.put(field, value); + } + } + + public void randomizeFieldsValues(Map record, Integer index) { + for (SObjectField field : this.fieldRandomizers.keySet()) { + Object value = this.fieldRandomizers.get(field).generate(index); + + if (value == null) { + continue; + } + + record.put(field.toString(), value); + } + } + } + + // Utilities + + @TestVisible + private class RandomIdGenerator { + private Integer counter = 0; + private Map prefixBySObject = new Map(); + + public Id get(SObjectType objectType) { + return this.get(this.getPrefix(objectType)); + } + + private Id get(String prefix) { + this.counter++; + return Id.valueOf(prefix + '0'.repeat(15 - prefix.length() - String.valueOf(this.counter).length()) + String.valueOf(this.counter)); + } + + private String getPrefix(SObjectType objectType) { + String prefix = this.prefixBySObject.get(objectType); + + if (prefix == null) { + prefix = objectType.getDescribe().getKeyPrefix(); + this.prefixBySObject.put(objectType, prefix); + } + + return prefix; + } + } + + public class TestModuleException extends Exception { + } } diff --git a/force-app/main/default/classes/TestModule_Test.cls b/force-app/main/default/classes/TestModule_Test.cls index 6a5ea27..6a7626f 100644 --- a/force-app/main/default/classes/TestModule_Test.cls +++ b/force-app/main/default/classes/TestModule_Test.cls @@ -1,6 +1,5 @@ @IsTest private class TestModule_Test { - // Builder @IsTest @@ -22,10 +21,7 @@ private class TestModule_Test { @IsTest static void builderSetFieldBySObjectField() { - Account acc = (Account) new AccountTestModule().Builder() - .set(Account.Name, 'Custom Name') - .set(Account.AnnualRevenue, 500000) - .build(); + Account acc = (Account) new AccountTestModule().Builder().set(Account.Name, 'Custom Name').set(Account.AnnualRevenue, 500000).build(); Assert.areEqual('Custom Name', acc.Name); Assert.areEqual(500000, acc.AnnualRevenue); @@ -33,10 +29,7 @@ private class TestModule_Test { @IsTest static void builderSetFieldByString() { - Account acc = (Account) new AccountTestModule().Builder() - .set('Name', 'String Field Name') - .set('Industry', 'Finance') - .build(); + Account acc = (Account) new AccountTestModule().Builder().set('Name', 'String Field Name').set('Industry', 'Finance').build(); Assert.areEqual('String Field Name', acc.Name); Assert.areEqual('Finance', acc.Industry); @@ -44,10 +37,7 @@ private class TestModule_Test { @IsTest static void builderWithConvenienceMethods() { - Account acc = (Account) new AccountTestModule().Builder() - .withName('Convenience Name') - .withIndustry('Healthcare') - .build(); + Account acc = (Account) new AccountTestModule().Builder().withName('Convenience Name').withIndustry('Healthcare').build(); Assert.areEqual('Convenience Name', acc.Name); Assert.areEqual('Healthcare', acc.Industry); @@ -55,9 +45,7 @@ private class TestModule_Test { @IsTest static void builderUseTemplate() { - Account acc = (Account) new AccountTestModule().Builder() - .enterprise() - .build(); + Account acc = (Account) new AccountTestModule().Builder().enterprise().build(); Assert.areEqual('Enterprise Account', acc.Name); Assert.areEqual(1000000, acc.AnnualRevenue); @@ -65,10 +53,7 @@ private class TestModule_Test { @IsTest static void builderTemplateOverride() { - Account acc = (Account) new AccountTestModule().Builder() - .enterprise() - .withName('Custom Enterprise') - .build(); + Account acc = (Account) new AccountTestModule().Builder().enterprise().withName('Custom Enterprise').build(); Assert.areEqual('Custom Enterprise', acc.Name); Assert.areEqual(1000000, acc.AnnualRevenue); @@ -96,9 +81,7 @@ private class TestModule_Test { @IsTest static void builderWithRandomizer() { - List accounts = new AccountTestModule().Builder() - .withRandomizer(new AccountRandomizer()) - .build(3); + List accounts = new AccountTestModule().Builder().withRandomizer(new AccountRandomizer()).build(3); Assert.areEqual(3, accounts.size()); Assert.areEqual('Company 0', accounts[0].get(Account.Name)); @@ -109,10 +92,8 @@ private class TestModule_Test { } @IsTest - static void builderWithSingleFieldRandomizer() { - List accounts = new AccountTestModule().Builder() - .withRandomizer(Account.Name, new NameRandomizer()) - .build(3); + static void builderWithFieldRandomizer() { + List accounts = new AccountTestModule().Builder().withRandomizer(Account.Name, new NameRandomizer()).build(3); Assert.areEqual(3, accounts.size()); Assert.areEqual('Name 0', accounts[0].get(Account.Name)); @@ -122,8 +103,9 @@ private class TestModule_Test { @IsTest static void builderWithListRandomizer() { - List accounts = new AccountTestModule().Builder() - .withRandomizer(Account.Industry, new TestModule.ListRandomizer(new List{'Tech', 'Finance', 'Health'})) + List accounts = new AccountTestModule() + .Builder() + .withRandomizer(Account.Industry, new TestModule.ListRandomizer(new List{ 'Tech', 'Finance', 'Health' })) .build(5); Assert.areEqual(5, accounts.size()); @@ -136,9 +118,10 @@ private class TestModule_Test { @IsTest static void builderCombinedRandomizers() { - List accounts = new AccountTestModule().Builder() + List accounts = new AccountTestModule() + .Builder() .withRandomizer(new AccountRandomizer()) - .withRandomizer(Account.Industry, new TestModule.ListRandomizer(new List{'Override'})) + .withRandomizer(Account.Industry, new TestModule.ListRandomizer(new List{ 'Override' })) .build(2); Assert.areEqual('Company 0', accounts[0].get(Account.Name)); @@ -147,10 +130,7 @@ private class TestModule_Test { @IsTest static void builderRandomizerPreservesUnsetFields() { - List accounts = new AccountTestModule().Builder() - .withIndustry('Finance') - .withRandomizer(new AccountRandomizer()) - .build(2); + List accounts = new AccountTestModule().Builder().withIndustry('Finance').withRandomizer(new AccountRandomizer()).build(2); Assert.areEqual('Finance', accounts[0].get(Account.Industry)); Assert.areEqual('Finance', accounts[1].get(Account.Industry)); @@ -189,15 +169,9 @@ private class TestModule_Test { @IsTest static void builderThenMockerIntegration() { - Account realAccount = (Account) new AccountTestModule().Builder() - .withName('Real Account') - .buildAndInsert(); + Account realAccount = (Account) new AccountTestModule().Builder().withName('Real Account').buildAndInsert(); - Contact mockContact = (Contact) new ContactTestModule().Mocker() - .withLastName('Mock Contact') - .withFakeId() - .set('Account.Name', realAccount.Name) - .build(); + Contact mockContact = (Contact) new ContactTestModule().Mocker().withLastName('Mock Contact').withFakeId().set('Account.Name', realAccount.Name).build(); Assert.isNotNull(realAccount.Id); Assert.isNotNull(mockContact.Id); @@ -206,14 +180,9 @@ private class TestModule_Test { @IsTest static void builderComplexScenario() { - Account enterprise = (Account) new AccountTestModule().Builder() - .enterprise() - .withName('Global Corp') - .buildAndInsert(); + Account enterprise = (Account) new AccountTestModule().Builder().enterprise().withName('Global Corp').buildAndInsert(); - List contacts = (List) new ContactTestModule().Builder() - .set(Contact.AccountId, enterprise.Id) - .buildAndInsert(2); + List contacts = (List) new ContactTestModule().Builder().set(Contact.AccountId, enterprise.Id).buildAndInsert(2); Assert.areEqual(2, contacts.size()); for (Contact c : contacts) { @@ -236,19 +205,14 @@ private class TestModule_Test { @IsTest static void mockerSetFieldBySObjectField() { - Account acc = (Account) new AccountTestModule().Mocker() - .set(Account.Name, 'Mock Account') - .build(); + Account acc = (Account) new AccountTestModule().Mocker().set(Account.Name, 'Mock Account').build(); Assert.areEqual('Mock Account', acc.Name); } @IsTest static void mockerSetFieldByString() { - Account acc = (Account) new AccountTestModule().Mocker() - .set('Name', 'String Mock') - .set('Industry', 'Healthcare') - .build(); + Account acc = (Account) new AccountTestModule().Mocker().set('Name', 'String Mock').set('Industry', 'Healthcare').build(); Assert.areEqual('String Mock', acc.Name); Assert.areEqual('Healthcare', acc.Industry); @@ -256,10 +220,7 @@ private class TestModule_Test { @IsTest static void mockerWithConvenienceMethods() { - Account acc = (Account) new AccountTestModule().Mocker() - .withName('Convenience Mock') - .withIndustry('Finance') - .build(); + Account acc = (Account) new AccountTestModule().Mocker().withName('Convenience Mock').withIndustry('Finance').build(); Assert.areEqual('Convenience Mock', acc.Name); Assert.areEqual('Finance', acc.Industry); @@ -267,18 +228,14 @@ private class TestModule_Test { @IsTest static void mockerNestedField() { - Account acc = (Account) new AccountTestModule().Mocker() - .withParentName('Parent Account') - .build(); + Account acc = (Account) new AccountTestModule().Mocker().withParentName('Parent Account').build(); Assert.areEqual('Parent Account', acc.Parent.Name); } @IsTest static void mockerDeeplyNestedField() { - Contact con = (Contact) new ContactTestModule().Mocker() - .set('Account.Parent.Name', 'Grandparent Account') - .build(); + Contact con = (Contact) new ContactTestModule().Mocker().set('Account.Parent.Name', 'Grandparent Account').build(); Assert.areEqual('Grandparent Account', con.Account.Parent.Name); } @@ -290,10 +247,7 @@ private class TestModule_Test { (Contact) new ContactTestModule().Mocker().withLastName('Jones').build() }; - Account acc = (Account) new AccountTestModule().Mocker() - .withName('Parent Account') - .withContacts(contacts) - .build(); + Account acc = (Account) new AccountTestModule().Mocker().withName('Parent Account').withContacts(contacts).build(); Assert.areEqual(2, acc.Contacts.size()); Assert.areEqual('Smith', acc.Contacts[0].LastName); @@ -302,17 +256,10 @@ private class TestModule_Test { @IsTest static void mockerMultipleChildRelationships() { - List contacts = new List{ - (Contact) new ContactTestModule().Mocker().withLastName('Smith').build() - }; - List opportunities = new List{ - new Opportunity(Name = 'Big Deal', StageName = 'Prospecting', CloseDate = Date.today()) - }; + List contacts = new List{ (Contact) new ContactTestModule().Mocker().withLastName('Smith').build() }; + List opportunities = new List{ new Opportunity(Name = 'Big Deal', StageName = 'Prospecting', CloseDate = Date.today()) }; - Account acc = (Account) new AccountTestModule().Mocker() - .withContacts(contacts) - .withOpportunities(opportunities) - .build(); + Account acc = (Account) new AccountTestModule().Mocker().withContacts(contacts).withOpportunities(opportunities).build(); Assert.areEqual(1, acc.Contacts.size()); Assert.areEqual(1, acc.Opportunities.size()); @@ -330,9 +277,7 @@ private class TestModule_Test { @IsTest static void mockerSetFakeId() { - Account acc = (Account) new AccountTestModule().Mocker() - .withFakeId() - .build(); + Account acc = (Account) new AccountTestModule().Mocker().withFakeId().build(); Assert.isNotNull(acc.Id); Assert.isTrue(String.valueOf(acc.Id).startsWith('001')); @@ -350,28 +295,21 @@ private class TestModule_Test { static void mockerSetReadOnly() { Datetime createdDate = Datetime.newInstance(2025, 1, 15, 10, 30, 0); - Account acc = (Account) new AccountTestModule().Mocker() - .withFakeId() - .withCreatedDate(createdDate) - .build(); + Account acc = (Account) new AccountTestModule().Mocker().withFakeId().withCreatedDate(createdDate).build(); Assert.areEqual(createdDate, acc.CreatedDate); } @IsTest static void mockerSetReadOnlyNestedField() { - Account acc = (Account) new AccountTestModule().Mocker() - .set('Owner.Name', 'System Admin') - .build(); + Account acc = (Account) new AccountTestModule().Mocker().set('Owner.Name', 'System Admin').build(); Assert.areEqual('System Admin', acc.Owner.Name); } @IsTest static void mockerWithBulkRandomizer() { - List accounts = new AccountTestModule().Mocker() - .withRandomizer(new AccountRandomizer()) - .build(3); + List accounts = new AccountTestModule().Mocker().withRandomizer(new AccountRandomizer()).build(3); Assert.areEqual(3, accounts.size()); Assert.areEqual('Company 0', accounts[0].get('Name')); @@ -380,10 +318,8 @@ private class TestModule_Test { } @IsTest - static void mockerWithSingleFieldRandomizer() { - List accounts = new AccountTestModule().Mocker() - .withRandomizer(Account.Name, new NameRandomizer()) - .build(3); + static void mockerWithFieldRandomizer() { + List accounts = new AccountTestModule().Mocker().withRandomizer(Account.Name, new NameRandomizer()).build(3); Assert.areEqual(3, accounts.size()); Assert.areEqual('Name 0', accounts[0].get('Name')); @@ -393,9 +329,7 @@ private class TestModule_Test { @IsTest static void mockerWithListRandomizer() { - List accounts = new AccountTestModule().Mocker() - .withRandomizer(Account.Industry, new TestModule.ListRandomizer(new List{'A', 'B'})) - .build(4); + List accounts = new AccountTestModule().Mocker().withRandomizer(Account.Industry, new TestModule.ListRandomizer(new List{ 'A', 'B' })).build(4); Assert.areEqual('A', accounts[0].get('Industry')); Assert.areEqual('B', accounts[1].get('Industry')); @@ -405,8 +339,8 @@ private class TestModule_Test { @IsTest static void fakeIdStaticUtility() { - Id accountId = TestModule.fakeId(Account.SObjectType); - Id contactId = TestModule.fakeId(Contact.SObjectType); + Id accountId = TestModule.IdGenerator.get(Account.SObjectType); + Id contactId = TestModule.IdGenerator.get(Contact.SObjectType); Assert.isTrue(String.valueOf(accountId).startsWith('001')); Assert.isTrue(String.valueOf(contactId).startsWith('003')); @@ -447,7 +381,6 @@ private class TestModule_Test { super.useTemplate('startup'); return this; } - } public class AccountMocker extends TestModule.RecordMocker { @@ -489,7 +422,6 @@ private class TestModule_Test { super.set('CreatedDate', createdDate); return this; } - } public class Templates implements TestModule.Template { @@ -537,26 +469,25 @@ private class TestModule_Test { } } - public class AccountRandomizer extends TestModule.RecordRandomizer { - public AccountRandomizer() { - this.add(Account.Name, new CompanyNameRandomizer()); - this.add(Account.AnnualRevenue, new RevenueRandomizer()); + public class AccountRandomizer implements TestModule.RecordRandomizer { + public Map randomizers() { + return new Map{ Account.Name => new CompanyNameRandomizer(), Account.AnnualRevenue => new RevenueRandomizer() }; } } - public class CompanyNameRandomizer implements TestModule.SingleFieldRandomizer { + public class CompanyNameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return 'Company ' + index; } } - public class RevenueRandomizer implements TestModule.SingleFieldRandomizer { + public class RevenueRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return (index + 1) * 100000; } } - public class NameRandomizer implements TestModule.SingleFieldRandomizer { + public class NameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return 'Name ' + index; } diff --git a/force-app/main/default/classes/concrete-modules/AccountTestModule.cls b/force-app/main/default/classes/concrete-modules/AccountTestModule.cls index ef0787b..7f043fe 100644 --- a/force-app/main/default/classes/concrete-modules/AccountTestModule.cls +++ b/force-app/main/default/classes/concrete-modules/AccountTestModule.cls @@ -83,7 +83,16 @@ public class AccountTestModule implements TestModule.BuilderProvider, TestModule } } - public class IndustryRandomizer implements TestModule.SingleFieldRandomizer { + public class AccountRandomizer implements TestModule.RecordRandomizer { + public Map randomizers() { + return new Map{ + Account.Name => new CompanyNameRandomizer(), + Account.Industry => new IndustryRandomizer() + }; + } + } + + public class IndustryRandomizer implements TestModule.FieldRandomizer { private List industries = new List{ 'Technology', 'Finance', 'Healthcare', 'Retail' }; public Object generate(Integer index) { @@ -91,16 +100,9 @@ public class AccountTestModule implements TestModule.BuilderProvider, TestModule } } - public class CompanyNameRandomizer implements TestModule.SingleFieldRandomizer { + public class CompanyNameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return 'Company ' + (index + 1); } } - - public class AccountRandomizer extends TestModule.RecordRandomizer { - public AccountRandomizer() { - this.add(Account.Name, new CompanyNameRandomizer()); - this.add(Account.Industry, new IndustryRandomizer()); - } - } } diff --git a/force-app/main/default/classes/concrete-modules/ContactTestModule.cls b/force-app/main/default/classes/concrete-modules/ContactTestModule.cls index ae8aab7..5640b7b 100644 --- a/force-app/main/default/classes/concrete-modules/ContactTestModule.cls +++ b/force-app/main/default/classes/concrete-modules/ContactTestModule.cls @@ -23,6 +23,11 @@ public class ContactTestModule implements TestModule.BuilderProvider, TestModule return this; } + public ContactBuilder withEmail(String email) { + super.set(Contact.Email, email); + return this; + } + public ContactBuilder business() { super.useTemplate('business'); return this; @@ -32,12 +37,47 @@ public class ContactTestModule implements TestModule.BuilderProvider, TestModule super.useTemplate('personal'); return this; } + + public ContactBuilder withContactRandomizer() { + super.withRandomizer(new ContactRandomizer()); + return this; + } } public class ContactMocker extends TestModule.RecordMocker { public ContactMocker() { super(new Contact(FirstName = 'Test', LastName = 'Contact', Email = 'test.contact@example.com')); } + + public ContactMocker withFirstName(String firstName) { + super.set(Contact.FirstName, firstName); + return this; + } + + public ContactMocker withLastName(String lastName) { + super.set(Contact.LastName, lastName); + return this; + } + + public ContactMocker withEmail(String email) { + super.set(Contact.Email, email); + return this; + } + + public ContactMocker withAccountName(String accountName) { + super.set('Account.Name', accountName); + return this; + } + + public ContactMocker withFakeId() { + super.setFakeId(); + return this; + } + + public ContactMocker withContactRandomizer() { + super.withRandomizer(new ContactRandomizer()); + return this; + } } public class Templates implements TestModule.Template { @@ -52,4 +92,27 @@ public class ContactTestModule implements TestModule.BuilderProvider, TestModule }; } } + + public class ContactRandomizer implements TestModule.RecordRandomizer { + public Map randomizers() { + return new Map{ + Contact.FirstName => new FirstNameRandomizer(), + Contact.LastName => new LastNameRandomizer() + }; + } + } + + public class FirstNameRandomizer implements TestModule.FieldRandomizer { + private List firstNames = new List{ 'John', 'Jane', 'Bob', 'Alice' }; + + public Object generate(Integer index) { + return firstNames[Math.mod(index, firstNames.size())]; + } + } + + public class LastNameRandomizer implements TestModule.FieldRandomizer { + public Object generate(Integer index) { + return 'Contact ' + (index + 1); + } + } } diff --git a/force-app/main/default/classes/concrete-modules/OpportunityTestModule.cls b/force-app/main/default/classes/concrete-modules/OpportunityTestModule.cls index 60177cf..58c01ec 100644 --- a/force-app/main/default/classes/concrete-modules/OpportunityTestModule.cls +++ b/force-app/main/default/classes/concrete-modules/OpportunityTestModule.cls @@ -43,12 +43,51 @@ public class OpportunityTestModule implements TestModule.BuilderProvider, TestMo return this; } + public OpportunityBuilder withOpportunityRandomizer() { + super.withRandomizer(new OpportunityRandomizer()); + return this; + } } public class OpportunityMocker extends TestModule.RecordMocker { public OpportunityMocker() { super(new Opportunity(Name = 'Test Opportunity', StageName = 'Prospecting', CloseDate = Date.today().addDays(30))); } + + public OpportunityMocker withName(String name) { + super.set(Opportunity.Name, name); + return this; + } + + public OpportunityMocker withStageName(String stageName) { + super.set(Opportunity.StageName, stageName); + return this; + } + + public OpportunityMocker withCloseDate(Date closeDate) { + super.set(Opportunity.CloseDate, closeDate); + return this; + } + + public OpportunityMocker withAmount(Decimal amount) { + super.set(Opportunity.Amount, amount); + return this; + } + + public OpportunityMocker withAccountName(String accountName) { + super.set('Account.Name', accountName); + return this; + } + + public OpportunityMocker withFakeId() { + super.setFakeId(); + return this; + } + + public OpportunityMocker withOpportunityRandomizer() { + super.withRandomizer(new OpportunityRandomizer()); + return this; + } } public class Templates implements TestModule.Template { @@ -63,4 +102,25 @@ public class OpportunityTestModule implements TestModule.BuilderProvider, TestMo }; } } + + public class OpportunityRandomizer implements TestModule.RecordRandomizer { + public Map randomizers() { + return new Map{ + Opportunity.Name => new OpportunityNameRandomizer(), + Opportunity.Amount => new AmountRandomizer() + }; + } + } + + public class OpportunityNameRandomizer implements TestModule.FieldRandomizer { + public Object generate(Integer index) { + return 'Opportunity ' + (index + 1); + } + } + + public class AmountRandomizer implements TestModule.FieldRandomizer { + public Object generate(Integer index) { + return (index + 1) * 50000; + } + } } \ No newline at end of file diff --git a/website/api.md b/website/api.md index 33d66a9..38f4167 100644 --- a/website/api.md +++ b/website/api.md @@ -68,7 +68,7 @@ public interface Builder { Builder set(String field, Object value); Builder useTemplate(String templateName); Builder withRandomizer(Randomizer randomizer); - Builder withRandomizer(SObjectField field, SingleFieldRandomizer randomizer); + Builder withRandomizer(SObjectField field, FieldRandomizer randomizer); SObject build(); SObject buildAndInsert(); @@ -88,7 +88,7 @@ public interface Mocker { Mocker setChildren(String relationship, List children); Mocker setFakeId(); Mocker withRandomizer(Randomizer randomizer); - Mocker withRandomizer(SObjectField field, SingleFieldRandomizer randomizer); + Mocker withRandomizer(SObjectField field, FieldRandomizer randomizer); SObject build(); List build(Integer amount); @@ -106,12 +106,12 @@ public interface Template { } ``` -### SingleFieldRandomizer +### FieldRandomizer Interface for generating random values for a single field. ```apex -public interface SingleFieldRandomizer { +public interface FieldRandomizer { Object generate(Integer index); } ``` @@ -151,7 +151,7 @@ public abstract class RecordBuilder implements Builder { | `set(String, Object)` | Set field value using field name | | `useTemplate(String)` | Apply a named template | | `withRandomizer(Randomizer)` | Set record randomizer | -| `withRandomizer(SObjectField, SingleFieldRandomizer)` | Set single field randomizer | +| `withRandomizer(SObjectField, FieldRandomizer)` | Set single field randomizer | | `build()` | Build single record (no DML) | | `buildAndInsert()` | Build and insert single record | | `build(Integer)` | Build multiple records | @@ -177,7 +177,7 @@ public abstract class RecordMocker implements Mocker { | `setChildren(String, List)` | Set child relationship records | | `setFakeId()` | Generate and set a fake ID | | `withRandomizer(Randomizer)` | Set record randomizer | -| `withRandomizer(SObjectField, SingleFieldRandomizer)` | Set single field randomizer | +| `withRandomizer(SObjectField, FieldRandomizer)` | Set single field randomizer | | `build()` | Build single mock record | | `build(Integer)` | Build multiple mock records | @@ -188,7 +188,7 @@ Virtual class for composing multiple field randomizers. ```apex public virtual class RecordRandomizer implements Randomizer { public RecordRandomizer setParent(Randomizer parent); - public RecordRandomizer add(SObjectField field, SingleFieldRandomizer randomizer); + public RecordRandomizer add(SObjectField field, FieldRandomizer randomizer); public Map generate(Integer index); } ``` @@ -198,7 +198,7 @@ public virtual class RecordRandomizer implements Randomizer { Built-in randomizer that cycles through a list of values. ```apex -public class ListRandomizer implements SingleFieldRandomizer { +public class ListRandomizer implements FieldRandomizer { public ListRandomizer(List values); public Object generate(Integer index); } diff --git a/website/best-practices.md b/website/best-practices.md index da99151..614c41a 100644 --- a/website/best-practices.md +++ b/website/best-practices.md @@ -195,7 +195,7 @@ static void testBulkAccountCreation() { ### Create Reusable Randomizers ```apex -public class PhoneRandomizer implements TestModule.SingleFieldRandomizer { +public class PhoneRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { String areaCode = String.valueOf(100 + Math.mod(index, 900)); String prefix = String.valueOf(100 + Math.mod(index * 7, 900)); diff --git a/website/builder.md b/website/builder.md index 78c5fc9..9a450c9 100644 --- a/website/builder.md +++ b/website/builder.md @@ -198,7 +198,7 @@ public AccountBuilder withRandomIndustry() { return this; } -public class IndustryRandomizer implements TestModule.SingleFieldRandomizer { +public class IndustryRandomizer implements TestModule.FieldRandomizer { private List industries = new List{ 'Technology', 'Finance', 'Healthcare', 'Retail' }; @@ -219,7 +219,7 @@ public AccountBuilder withAccountRandomizer() { return this; } -public class AccountRandomizer extends TestModule.RecordRandomizer { +public class AccountRandomizer implements TestModule.RecordRandomizer { public AccountRandomizer() { this.add(Account.Name, new NameRandomizer()); this.add(Account.Industry, new IndustryRandomizer()); @@ -315,7 +315,7 @@ public class AccountTestModule implements TestModule.BuilderProvider { } } - public class IndustryRandomizer implements TestModule.SingleFieldRandomizer { + public class IndustryRandomizer implements TestModule.FieldRandomizer { private List industries = new List{ 'Technology', 'Finance', 'Healthcare', 'Retail' }; @@ -325,13 +325,13 @@ public class AccountTestModule implements TestModule.BuilderProvider { } } - public class NameRandomizer implements TestModule.SingleFieldRandomizer { + public class NameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return 'Company ' + (index + 1); } } - public class AccountRandomizer extends TestModule.RecordRandomizer { + public class AccountRandomizer implements TestModule.RecordRandomizer { public AccountRandomizer() { this.add(Account.Name, new NameRandomizer()); this.add(Account.Industry, new IndustryRandomizer()); diff --git a/website/examples.md b/website/examples.md index b6acd1b..0ac6bb8 100644 --- a/website/examples.md +++ b/website/examples.md @@ -97,7 +97,7 @@ static void testRecordRandomizer() { ### Custom Randomizer ```apex -public class EmailRandomizer implements TestModule.SingleFieldRandomizer { +public class EmailRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return 'user' + (index + 1) + '@example.com'; } diff --git a/website/mocker.md b/website/mocker.md index bf70f20..ab45233 100644 --- a/website/mocker.md +++ b/website/mocker.md @@ -261,7 +261,7 @@ public class AccountTestModule implements TestModule.MockerProvider { } } - public class IndustryRandomizer implements TestModule.SingleFieldRandomizer { + public class IndustryRandomizer implements TestModule.FieldRandomizer { private List industries = new List{ 'Technology', 'Finance', 'Healthcare', 'Retail' }; diff --git a/website/randomizers.md b/website/randomizers.md index 55d0434..7fa2a35 100644 --- a/website/randomizers.md +++ b/website/randomizers.md @@ -11,12 +11,12 @@ Randomizers help you: - Generate realistic test data - Cycle through predefined value sets -## SingleFieldRandomizer Interface +## FieldRandomizer Interface For generating values for a single field: ```apex -public interface SingleFieldRandomizer { +public interface FieldRandomizer { Object generate(Integer index); } ``` @@ -26,7 +26,7 @@ The `index` parameter indicates which record is being generated (0-based). ### Basic Implementation ```apex -public class NameRandomizer implements TestModule.SingleFieldRandomizer { +public class NameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return 'Company ' + (index + 1); } @@ -38,7 +38,7 @@ public class NameRandomizer implements TestModule.SingleFieldRandomizer { ### Cycling Through Values ```apex -public class IndustryRandomizer implements TestModule.SingleFieldRandomizer { +public class IndustryRandomizer implements TestModule.FieldRandomizer { private List industries = new List{ 'Technology', 'Finance', 'Healthcare', 'Retail' }; @@ -84,10 +84,10 @@ public class AccountRandomizer implements TestModule.Randomizer { ## RecordRandomizer Class -A helper class for composing multiple SingleFieldRandomizers: +A helper class for composing multiple FieldRandomizers: ```apex -public class AccountRandomizer extends TestModule.RecordRandomizer { +public class AccountRandomizer implements TestModule.RecordRandomizer { public AccountRandomizer() { this.add(Account.Name, new NameRandomizer()); this.add(Account.Industry, new IndustryRandomizer()); @@ -99,7 +99,7 @@ public class AccountRandomizer extends TestModule.RecordRandomizer { ### Chaining Randomizers ```apex -public class FullAccountRandomizer extends TestModule.RecordRandomizer { +public class FullAccountRandomizer implements TestModule.RecordRandomizer { public FullAccountRandomizer() { // Base randomizer this.setParent(new BasicAccountRandomizer()); @@ -178,7 +178,7 @@ List accounts = AccountTestModule.Mocker() ### Sequential Names ```apex -public class CompanyNameRandomizer implements TestModule.SingleFieldRandomizer { +public class CompanyNameRandomizer implements TestModule.FieldRandomizer { private String prefix; public CompanyNameRandomizer() { @@ -202,7 +202,7 @@ public class CompanyNameRandomizer implements TestModule.SingleFieldRandomizer { ### Email Generator ```apex -public class EmailRandomizer implements TestModule.SingleFieldRandomizer { +public class EmailRandomizer implements TestModule.FieldRandomizer { private String domain; public EmailRandomizer() { @@ -224,7 +224,7 @@ public class EmailRandomizer implements TestModule.SingleFieldRandomizer { ### Phone Number Generator ```apex -public class PhoneRandomizer implements TestModule.SingleFieldRandomizer { +public class PhoneRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { String areaCode = String.valueOf(100 + Math.mod(index, 900)).leftPad(3, '0'); String prefix = String.valueOf(100 + Math.mod(index * 7, 900)).leftPad(3, '0'); @@ -239,7 +239,7 @@ public class PhoneRandomizer implements TestModule.SingleFieldRandomizer { ### Date Generator ```apex -public class CloseDateRandomizer implements TestModule.SingleFieldRandomizer { +public class CloseDateRandomizer implements TestModule.FieldRandomizer { private Date baseDate; private Integer dayIncrement; @@ -259,7 +259,7 @@ public class CloseDateRandomizer implements TestModule.SingleFieldRandomizer { ### Amount Generator ```apex -public class AmountRandomizer implements TestModule.SingleFieldRandomizer { +public class AmountRandomizer implements TestModule.FieldRandomizer { private Decimal baseAmount; private Decimal multiplier; @@ -279,7 +279,7 @@ public class AmountRandomizer implements TestModule.SingleFieldRandomizer { ### Picklist Cycler ```apex -public class StageRandomizer implements TestModule.SingleFieldRandomizer { +public class StageRandomizer implements TestModule.FieldRandomizer { private List stages = new List{ 'Prospecting', 'Qualification', @@ -298,7 +298,7 @@ public class StageRandomizer implements TestModule.SingleFieldRandomizer { ## Composite Randomizer Example ```apex -public class FullContactRandomizer extends TestModule.RecordRandomizer { +public class FullContactRandomizer implements TestModule.RecordRandomizer { public FullContactRandomizer() { this.add(Contact.FirstName, new FirstNameRandomizer()); this.add(Contact.LastName, new LastNameRandomizer()); @@ -308,7 +308,7 @@ public class FullContactRandomizer extends TestModule.RecordRandomizer { } } -public class FirstNameRandomizer implements TestModule.SingleFieldRandomizer { +public class FirstNameRandomizer implements TestModule.FieldRandomizer { private List names = new List{ 'John', 'Jane', 'Bob', 'Alice', 'Charlie', 'Diana' }; @@ -318,7 +318,7 @@ public class FirstNameRandomizer implements TestModule.SingleFieldRandomizer { } } -public class LastNameRandomizer implements TestModule.SingleFieldRandomizer { +public class LastNameRandomizer implements TestModule.FieldRandomizer { private List names = new List{ 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia' }; @@ -328,7 +328,7 @@ public class LastNameRandomizer implements TestModule.SingleFieldRandomizer { } } -public class TitleRandomizer implements TestModule.SingleFieldRandomizer { +public class TitleRandomizer implements TestModule.FieldRandomizer { private List titles = new List{ 'CEO', 'CTO', 'CFO', 'VP Sales', 'Director', 'Manager' }; From 796e5ae728481c63bc249a83db60f7e60d176a22 Mon Sep 17 00:00:00 2001 From: Piotr PG Gajek Date: Mon, 2 Feb 2026 22:23:58 +0100 Subject: [PATCH 2/2] Documentation update --- README.md | 57 +++-- website/api.md | 140 ++++++++---- website/best-practices.md | 117 +++++------ website/builder.md | 105 ++++----- website/examples.md | 421 ++++++++++++++++++++----------------- website/getting-started.md | 47 +++-- website/index.md | 30 +-- website/installation.md | 62 ++++-- website/mocker.md | 165 ++++++++------- website/randomizers.md | 319 ++++++++++++---------------- website/templates.md | 254 +++++++--------------- 11 files changed, 851 insertions(+), 866 deletions(-) diff --git a/README.md b/README.md index 6883eda..313e748 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ For comprehensive documentation, visit [https://beyond-the-cloud-dev.github.io/t - **Randomizers** - Generate unique field values for bulk data creation - **Parent Relations** - Mock parent lookups (e.g., `Account.Parent.Name`) - **Child Relations** - Mock child collections (e.g., `Account.Contacts`) -- **Fake IDs** - Generate valid-looking IDs without database operations +- **Fake IDs** - Generate valid-looking IDs via `TestModule.IdGenerator` ## Quick Start @@ -49,8 +49,8 @@ static void testWithRealRecords() { .withAccountRandomizer() .buildAndInsert(100); - System.assertNotEquals(null, acc.Id); - System.assertEquals(100, accounts.size()); + Assert.isNotNull(acc.Id); + Assert.areEqual(100, accounts.size()); } ``` @@ -70,13 +70,13 @@ static void testWithMockedRecords() { .build(); // Mock with child records - List contacts = ContactTestModule.Mocker().build(3); + List contacts = (List) ContactTestModule.Mocker().build(3); Account accWithContacts = (Account) AccountTestModule.Mocker() .withContacts(contacts) .build(); - System.assertEquals('Parent Corp', accWithParent.Parent.Name); - System.assertEquals(3, accWithContacts.Contacts.size()); + Assert.areEqual('Parent Corp', accWithParent.Parent.Name); + Assert.areEqual(3, accWithContacts.Contacts.size()); } ``` @@ -124,7 +124,7 @@ npm run docs:dev ## Project Structure ``` -force-app/main/default/classes/test-module/ +force-app/main/default/classes/ ├── TestModule.cls # Core framework ├── TestModule.cls-meta.xml ├── TestModule_Test.cls # Framework tests @@ -151,7 +151,7 @@ public class AccountTestModule { public class AccountBuilder extends TestModule.RecordBuilder { public AccountBuilder() { - super(new Account(Name = 'Test Account')); + super(new Templates()); } public AccountBuilder withName(String name) { @@ -159,21 +159,39 @@ public class AccountTestModule { return this; } - public AccountBuilder withIndustry(String industry) { - super.set(Account.Industry, industry); + public AccountBuilder enterprise() { + super.useTemplate('enterprise'); return this; } } public class AccountMocker extends TestModule.RecordMocker { public AccountMocker() { - super(new Account(Name = 'Test Account')); + super(new Account(Name = 'Test Account', Industry = 'Technology')); } public AccountMocker withContacts(List contacts) { super.setChildren('Contacts', contacts); return this; } + + public AccountMocker withParentName(String parentName) { + super.set('Parent.Name', parentName); + return this; + } + } + + public class Templates implements TestModule.Template { + public SObject defaultTemplate() { + return new Account(Name = 'Test Account', Industry = 'Technology'); + } + + public Map templates() { + return new Map{ + 'enterprise' => new Account(Name = 'Enterprise Account', AnnualRevenue = 1000000), + 'startup' => new Account(Name = 'Startup Account', AnnualRevenue = 100000) + }; + } } } ``` @@ -184,8 +202,10 @@ public class AccountTestModule { // Builder - creates real records for database insertion public interface Builder { Builder set(SObjectField field, Object value); + Builder set(String field, Object value); Builder useTemplate(String templateName); - Builder withRandomizer(Randomizer randomizer); + Builder withRandomizer(TestModule.RecordRandomizer randomizer); + Builder withRandomizer(SObjectField field, TestModule.FieldRandomizer randomizer); SObject build(); SObject buildAndInsert(); List build(Integer amount); @@ -195,11 +215,24 @@ public interface Builder { // Mocker - creates in-memory records without DML public interface Mocker { Mocker set(SObjectField field, Object value); + Mocker set(String field, Object value); // Supports dot notation for parent relationships Mocker setChildren(String relationship, List children); Mocker setFakeId(); + Mocker withRandomizer(TestModule.RecordRandomizer randomizer); + Mocker withRandomizer(SObjectField field, TestModule.FieldRandomizer randomizer); SObject build(); List build(Integer amount); } + +// FieldRandomizer - generates values for a single field +public interface FieldRandomizer { + Object generate(Integer index); +} + +// RecordRandomizer - generates values for multiple fields +public interface RecordRandomizer { + Map randomizers(); +} ``` ## Contributors diff --git a/website/api.md b/website/api.md index 38f4167..d35d7df 100644 --- a/website/api.md +++ b/website/api.md @@ -10,30 +10,55 @@ The core class containing all interfaces and base implementations. @IsTest @TestVisible private class TestModule { - // Interfaces and implementations + // Interfaces, base classes, and utilities } ``` -## Static Methods +## Static Utilities + +### IdGenerator + +A static utility for generating fake IDs. + +```apex +public static final RandomIdGenerator IdGenerator +``` + +**Usage:** + +```apex +Id fakeAccountId = TestModule.IdGenerator.get(Account.SObjectType); +Id fakeContactId = TestModule.IdGenerator.get(Contact.SObjectType); + +// Returns valid-looking IDs like 001000000000001, 003000000000002 +``` ### fakeId() -Generate a fake ID for an SObject type. +Convenience method for generating fake IDs. ```apex -public static Id fakeId(SObjectType sot) +public static Id fakeId(SObjectType objectType) ``` -**Parameters:** -- `sot` - The SObjectType to generate an ID for +**Example:** -**Returns:** A valid-looking fake ID +```apex +Id fakeId = TestModule.fakeId(Account.SObjectType); +``` + +### ListRandomizer() + +Factory method for creating a list-based randomizer. + +```apex +public static FieldRandomizer ListRandomizer(List values) +``` **Example:** ```apex -Id fakeAccountId = TestModule.fakeId(Account.SObjectType); -// Returns: 001000000000001 +TestModule.ListRandomizer(new List{ 'Technology', 'Finance', 'Healthcare' }) ``` ## Interfaces @@ -67,8 +92,8 @@ public interface Builder { Builder set(SObjectField field, Object value); Builder set(String field, Object value); Builder useTemplate(String templateName); - Builder withRandomizer(Randomizer randomizer); - Builder withRandomizer(SObjectField field, FieldRandomizer randomizer); + Builder withRandomizer(TestModule.RecordRandomizer randomizer); + Builder withRandomizer(SObjectField field, TestModule.FieldRandomizer randomizer); SObject build(); SObject buildAndInsert(); @@ -84,11 +109,11 @@ Interface for building in-memory SObject mocks. ```apex public interface Mocker { Mocker set(SObjectField field, Object value); - Mocker set(String field, Object value); + Mocker set(String field, Object value); // Supports dot notation for parent relationships Mocker setChildren(String relationship, List children); Mocker setFakeId(); - Mocker withRandomizer(Randomizer randomizer); - Mocker withRandomizer(SObjectField field, FieldRandomizer randomizer); + Mocker withRandomizer(TestModule.RecordRandomizer randomizer); + Mocker withRandomizer(SObjectField field, TestModule.FieldRandomizer randomizer); SObject build(); List build(Integer amount); @@ -116,13 +141,13 @@ public interface FieldRandomizer { } ``` -### Randomizer +### RecordRandomizer Interface for generating random values for multiple fields. ```apex -public interface Randomizer { - Map generate(Integer index); +public interface RecordRandomizer { + Map randomizers(); } ``` @@ -141,7 +166,7 @@ public abstract class RecordBuilder implements Builder { **Constructors:** - `RecordBuilder(SObject prototype)` - Initialize with a prototype record -- `RecordBuilder(Template templates)` - Initialize with templates +- `RecordBuilder(Template templates)` - Initialize with templates (uses defaultTemplate) **Methods:** @@ -150,8 +175,8 @@ public abstract class RecordBuilder implements Builder { | `set(SObjectField, Object)` | Set field value using field token | | `set(String, Object)` | Set field value using field name | | `useTemplate(String)` | Apply a named template | -| `withRandomizer(Randomizer)` | Set record randomizer | -| `withRandomizer(SObjectField, FieldRandomizer)` | Set single field randomizer | +| `withRandomizer(RecordRandomizer)` | Add record randomizer | +| `withRandomizer(SObjectField, FieldRandomizer)` | Add single field randomizer | | `build()` | Build single record (no DML) | | `buildAndInsert()` | Build and insert single record | | `build(Integer)` | Build multiple records | @@ -173,26 +198,14 @@ public abstract class RecordMocker implements Mocker { | Method | Description | |--------|-------------| | `set(SObjectField, Object)` | Set field value using field token | -| `set(String, Object)` | Set field value using field name (supports dot notation) | +| `set(String, Object)` | Set field value (supports dot notation for parent relationships) | | `setChildren(String, List)` | Set child relationship records | | `setFakeId()` | Generate and set a fake ID | -| `withRandomizer(Randomizer)` | Set record randomizer | -| `withRandomizer(SObjectField, FieldRandomizer)` | Set single field randomizer | +| `withRandomizer(RecordRandomizer)` | Add record randomizer | +| `withRandomizer(SObjectField, FieldRandomizer)` | Add single field randomizer | | `build()` | Build single mock record | | `build(Integer)` | Build multiple mock records | -### RecordRandomizer - -Virtual class for composing multiple field randomizers. - -```apex -public virtual class RecordRandomizer implements Randomizer { - public RecordRandomizer setParent(Randomizer parent); - public RecordRandomizer add(SObjectField field, FieldRandomizer randomizer); - public Map generate(Integer index); -} -``` - ### ListRandomizer Built-in randomizer that cycles through a list of values. @@ -207,7 +220,11 @@ public class ListRandomizer implements FieldRandomizer { **Example:** ```apex -new TestModule.ListRandomizer(new List{'Technology', 'Finance', 'Healthcare'}) +// Using static factory method +TestModule.ListRandomizer(new List{ 'Technology', 'Finance', 'Healthcare' }) + +// Or instantiate directly +new TestModule.ListRandomizer(new List{ 'A', 'B', 'C' }) ``` ## Method Details @@ -231,7 +248,7 @@ AccountTestModule.Builder() ### set(String, Object) -Set a field value using a string field name. Supports dot notation for parent relationships in Mocker. +Set a field value using a string field name. In Mocker, supports dot notation for parent relationships. ```apex Builder set(String field, Object value) @@ -248,6 +265,12 @@ AccountTestModule.Builder() // Parent relationship (Mocker only) AccountTestModule.Mocker() .set('Parent.Name', 'Parent Corp') + .set('Owner.Name', 'John Doe') + .build(); + +// Deeply nested (Mocker only) +ContactTestModule.Mocker() + .set('Account.Parent.Name', 'Grandparent Corp') .build(); ``` @@ -282,14 +305,14 @@ Mocker setChildren(String relationship, List children) **Example:** ```apex -List contacts = ContactTestModule.Mocker().build(3); +List contacts = ContactTestModule.Mocker().build(3); Account acc = (Account) AccountTestModule.Mocker() - .setChildren('Contacts', contacts) + .setChildren('Contacts', (List) contacts) .build(); // Access children -System.assertEquals(3, acc.Contacts.size()); +Assert.areEqual(3, acc.Contacts.size()); ``` ### setFakeId() @@ -308,6 +331,39 @@ Account acc = (Account) AccountTestModule.Mocker() .build(); // acc.Id is now a valid-looking fake ID like 001000000000001 +Assert.isTrue(String.valueOf(acc.Id).startsWith('001')); +``` + +### withRandomizer(RecordRandomizer) + +Add a record randomizer that generates values for multiple fields. + +```apex +Builder withRandomizer(TestModule.RecordRandomizer randomizer) +``` + +**Example:** + +```apex +List accounts = AccountTestModule.Builder() + .withRandomizer(new AccountRandomizer()) + .build(10); +``` + +### withRandomizer(SObjectField, FieldRandomizer) + +Add a field randomizer for a specific field. + +```apex +Builder withRandomizer(SObjectField field, TestModule.FieldRandomizer randomizer) +``` + +**Example:** + +```apex +List accounts = AccountTestModule.Builder() + .withRandomizer(Account.Industry, TestModule.ListRandomizer(new List{ 'Tech', 'Finance' })) + .build(10); ``` ### build() / build(Integer) @@ -326,7 +382,7 @@ List build(Integer amount) Account acc = (Account) AccountTestModule.Builder().build(); // Multiple records -List accounts = AccountTestModule.Builder().build(10); +List accounts = AccountTestModule.Builder().build(10); ``` ### buildAndInsert() / buildAndInsert(Integer) @@ -345,7 +401,7 @@ List buildAndInsert(Integer amount) Account acc = (Account) AccountTestModule.Builder().buildAndInsert(); // Multiple records with randomizer -List accounts = AccountTestModule.Builder() +List accounts = AccountTestModule.Builder() .withAccountRandomizer() .buildAndInsert(100); ``` diff --git a/website/best-practices.md b/website/best-practices.md index 614c41a..e3482f9 100644 --- a/website/best-practices.md +++ b/website/best-practices.md @@ -26,7 +26,7 @@ static void integrationTest() { update acc; Account reloaded = [SELECT Rating FROM Account WHERE Id = :acc.Id]; - System.assertEquals('Hot', reloaded.Rating); + Assert.areEqual('Hot', reloaded.Rating); } ``` @@ -50,7 +50,7 @@ static void unitTest() { // Test pure logic without database String tier = AccountClassifier.getTier(acc); - System.assertEquals('Enterprise', tier); + Assert.areEqual('Enterprise', tier); } ``` @@ -86,15 +86,14 @@ public AccountBuilder enterprise() { return this; } -public AccountBuilder active() { - super.set(Account.IsActive__c, true); +public OpportunityBuilder closedWon() { + super.useTemplate('closedWon'); return this; } // Usage reads like a sentence AccountTestModule.Builder() .enterprise() - .active() .buildAndInsert(); ``` @@ -102,16 +101,14 @@ AccountTestModule.Builder() ```apex // Good - sets related fields together -public OpportunityBuilder wonDeal(Decimal amount) { - super.set(Opportunity.StageName, 'Closed Won'); - super.set(Opportunity.Amount, amount); - super.set(Opportunity.CloseDate, Date.today()); +public OpportunityBuilder closedWon() { + super.useTemplate('closedWon'); // Sets StageName, CloseDate, Amount return this; } // Usage OpportunityTestModule.Builder() - .wonDeal(100000) + .closedWon() .buildAndInsert(); ``` @@ -130,27 +127,13 @@ public class Templates implements TestModule.Template { public Map templates() { return new Map{ - // Business scenarios 'enterprise' => new Account( Name = 'Enterprise Account', - AnnualRevenue = 1000000, - NumberOfEmployees = 500 + AnnualRevenue = 1000000 ), 'startup' => new Account( Name = 'Startup Account', - AnnualRevenue = 100000, - NumberOfEmployees = 10 - ), - - // Account types - 'partner' => new Account( - Name = 'Partner Account', - Type = 'Partner' - ), - 'prospect' => new Account( - Name = 'Prospect Account', - Type = 'Prospect', - Rating = 'Warm' + AnnualRevenue = 100000 ) }; } @@ -167,40 +150,42 @@ static void testEnterpriseWithCustomName() { .withName('Custom Corp') // Override specific field .buildAndInsert(); - System.assertEquals('Custom Corp', acc.Name); - System.assertEquals(1000000, acc.AnnualRevenue); + Assert.areEqual('Custom Corp', acc.Name); + Assert.areEqual(1000000, acc.AnnualRevenue); } ``` -## Randomizers for Bulk Data +## Implement RecordRandomizer Correctly -### Use for Unique Values +### Return Map of FieldRandomizers ```apex -@IsTest -static void testBulkAccountCreation() { - List accounts = AccountTestModule.Builder() - .withAccountRandomizer() - .buildAndInsert(100); - - // Each account has unique name - Set names = new Set(); - for (Account acc : accounts) { - names.add(acc.Name); +public class AccountRandomizer implements TestModule.RecordRandomizer { + public Map randomizers() { + return new Map{ + Account.Name => new CompanyNameRandomizer(), + Account.Industry => new IndustryRandomizer() + }; } - System.assertEquals(100, names.size()); } ``` -### Create Reusable Randomizers +### Create Reusable FieldRandomizers ```apex -public class PhoneRandomizer implements TestModule.FieldRandomizer { +public class CompanyNameRandomizer implements TestModule.FieldRandomizer { + public Object generate(Integer index) { + return 'Company ' + (index + 1); + } +} + +public class IndustryRandomizer implements TestModule.FieldRandomizer { + private List industries = new List{ + 'Technology', 'Finance', 'Healthcare', 'Retail' + }; + public Object generate(Integer index) { - String areaCode = String.valueOf(100 + Math.mod(index, 900)); - String prefix = String.valueOf(100 + Math.mod(index * 7, 900)); - String suffix = String.valueOf(1000 + index); - return '(' + areaCode + ') ' + prefix + '-' + suffix; + return industries[Math.mod(index, industries.size())]; } } ``` @@ -213,12 +198,12 @@ public class PhoneRandomizer implements TestModule.FieldRandomizer { @IsTest static void testWithParentData() { Contact con = (Contact) ContactTestModule.Mocker() - .setFakeId() + .withFakeId() .set('Account.Name', 'Parent Account') .set('Account.Industry', 'Technology') .build(); - System.assertEquals('Parent Account', con.Account.Name); + Assert.areEqual('Parent Account', con.Account.Name); } ``` @@ -227,17 +212,14 @@ static void testWithParentData() { ```apex @IsTest static void testWithChildData() { - List contacts = ContactTestModule.Mocker().build(5); - List opps = OpportunityTestModule.Mocker().build(3); + List contacts = (List) ContactTestModule.Mocker().build(5); Account acc = (Account) AccountTestModule.Mocker() .setFakeId() .setChildren('Contacts', contacts) - .setChildren('Opportunities', opps) .build(); - System.assertEquals(5, acc.Contacts.size()); - System.assertEquals(3, acc.Opportunities.size()); + Assert.areEqual(5, acc.Contacts.size()); } ``` @@ -246,12 +228,10 @@ static void testWithChildData() { ### One Module Per SObject ``` -test-module/ -├── TestModule.cls # Framework -└── concrete-modules/ - ├── AccountTestModule.cls # Account builders - ├── ContactTestModule.cls # Contact builders - └── OpportunityTestModule.cls +concrete-modules/ +├── AccountTestModule.cls +├── ContactTestModule.cls +└── OpportunityTestModule.cls ``` ### Consistent Naming @@ -265,6 +245,10 @@ public class AccountBuilder extends TestModule.RecordBuilder { ... } // Mocker: {SObject}Mocker public class AccountMocker extends TestModule.RecordMocker { ... } + +// Randomizer: {SObject}Randomizer or {Field}Randomizer +public class AccountRandomizer implements TestModule.RecordRandomizer { ... } +public class IndustryRandomizer implements TestModule.FieldRandomizer { ... } ``` ## Performance Tips @@ -294,7 +278,7 @@ static void fastTest() { ```apex // Good - single DML -List accounts = AccountTestModule.Builder() +List accounts = AccountTestModule.Builder() .buildAndInsert(100); // Avoid - multiple DML @@ -337,14 +321,16 @@ private class AccountServiceTest { ```apex @IsTest static void testMissingTemplate() { + Boolean exceptionThrown = false; try { AccountTestModule.Builder() .useTemplate('nonexistent') .build(); - System.assert(false, 'Should have thrown exception'); } catch (TestModule.TestModuleException e) { - System.assert(e.getMessage().contains('not found')); + exceptionThrown = true; + Assert.isTrue(e.getMessage().contains('not found')); } + Assert.isTrue(exceptionThrown, 'Expected TestModuleException'); } ``` @@ -357,9 +343,8 @@ static void testMissingTemplate() { * Test data builder for Account records. * * Templates: - * - enterprise: Large company with 500+ employees - * - startup: Small company with <50 employees - * - partner: Partner account type + * - enterprise: Large company with $1M+ revenue + * - startup: Small company with $100K revenue * * @example * Account acc = (Account) AccountTestModule.Builder() diff --git a/website/builder.md b/website/builder.md index 9a450c9..79cf7a4 100644 --- a/website/builder.md +++ b/website/builder.md @@ -54,7 +54,7 @@ Account acc = (Account) AccountTestModule.Builder() .build(); // acc.Id is null - not inserted -System.assertEquals(null, acc.Id); +Assert.isNull(acc.Id); ``` ### Single Record (With Insert) @@ -65,31 +65,31 @@ Account acc = (Account) AccountTestModule.Builder() .buildAndInsert(); // acc.Id is set - record is in database -System.assertNotEquals(null, acc.Id); +Assert.isNotNull(acc.Id); ``` ### Multiple Records (No DML) ```apex -List accounts = AccountTestModule.Builder() +List accounts = AccountTestModule.Builder() .withIndustry('Technology') .build(10); // 10 records, none inserted -System.assertEquals(10, accounts.size()); -System.assertEquals(null, accounts[0].Id); +Assert.areEqual(10, accounts.size()); +Assert.isNull(accounts[0].Id); ``` ### Multiple Records (With Insert) ```apex -List accounts = AccountTestModule.Builder() +List accounts = AccountTestModule.Builder() .withIndustry('Technology') .buildAndInsert(10); // 10 records, all inserted -System.assertEquals(10, accounts.size()); -System.assertNotEquals(null, accounts[0].Id); +Assert.areEqual(10, accounts.size()); +Assert.isNotNull(accounts[0].Id); ``` ## Setting Field Values @@ -141,14 +141,12 @@ public class Templates implements TestModule.Template { 'enterprise' => new Account( Name = 'Enterprise Account', Industry = 'Technology', - AnnualRevenue = 1000000, - NumberOfEmployees = 500 + AnnualRevenue = 1000000 ), 'startup' => new Account( Name = 'Startup Account', Industry = 'Technology', - AnnualRevenue = 100000, - NumberOfEmployees = 10 + AnnualRevenue = 100000 ) }; } @@ -182,15 +180,15 @@ Account acc = (Account) AccountTestModule.Builder() .buildAndInsert(); // Has template values plus custom name -System.assertEquals('Custom Enterprise', acc.Name); -System.assertEquals(1000000, acc.AnnualRevenue); +Assert.areEqual('Custom Enterprise', acc.Name); +Assert.areEqual(1000000, acc.AnnualRevenue); ``` ## Using Randomizers -Randomizers generate unique values for each record: +Randomizers generate unique values for each record when building multiple records. -### Single Field Randomizer +### FieldRandomizer - Single Field ```apex public AccountBuilder withRandomIndustry() { @@ -209,9 +207,7 @@ public class IndustryRandomizer implements TestModule.FieldRandomizer { } ``` -### Record Randomizer - -For multiple fields: +### RecordRandomizer - Multiple Fields ```apex public AccountBuilder withAccountRandomizer() { @@ -220,23 +216,34 @@ public AccountBuilder withAccountRandomizer() { } public class AccountRandomizer implements TestModule.RecordRandomizer { - public AccountRandomizer() { - this.add(Account.Name, new NameRandomizer()); - this.add(Account.Industry, new IndustryRandomizer()); + public Map randomizers() { + return new Map{ + Account.Name => new CompanyNameRandomizer(), + Account.Industry => new IndustryRandomizer() + }; + } +} + +public class CompanyNameRandomizer implements TestModule.FieldRandomizer { + public Object generate(Integer index) { + return 'Company ' + (index + 1); } } ``` -### Using Randomizers +### Using Built-in ListRandomizer ```apex -List accounts = AccountTestModule.Builder() - .withAccountRandomizer() - .buildAndInsert(100); - -// Each account has unique name and cycling industry -System.assertEquals('Company 1', accounts[0].Name); -System.assertEquals('Company 2', accounts[1].Name); +List accounts = AccountTestModule.Builder() + .withRandomizer(Account.Industry, + TestModule.ListRandomizer(new List{ 'Tech', 'Finance', 'Health' })) + .build(5); + +// Industries cycle: Tech, Finance, Health, Tech, Finance +Assert.areEqual('Tech', accounts[0].get('Industry')); +Assert.areEqual('Finance', accounts[1].get('Industry')); +Assert.areEqual('Health', accounts[2].get('Industry')); +Assert.areEqual('Tech', accounts[3].get('Industry')); ``` ## Complete Builder Example @@ -266,11 +273,6 @@ public class AccountTestModule implements TestModule.BuilderProvider { return this; } - public AccountBuilder withRevenue(Decimal revenue) { - super.set(Account.AnnualRevenue, revenue); - return this; - } - // Template shortcuts public AccountBuilder enterprise() { super.useTemplate('enterprise'); @@ -315,6 +317,15 @@ public class AccountTestModule implements TestModule.BuilderProvider { } } + public class AccountRandomizer implements TestModule.RecordRandomizer { + public Map randomizers() { + return new Map{ + Account.Name => new CompanyNameRandomizer(), + Account.Industry => new IndustryRandomizer() + }; + } + } + public class IndustryRandomizer implements TestModule.FieldRandomizer { private List industries = new List{ 'Technology', 'Finance', 'Healthcare', 'Retail' @@ -325,18 +336,11 @@ public class AccountTestModule implements TestModule.BuilderProvider { } } - public class NameRandomizer implements TestModule.FieldRandomizer { + public class CompanyNameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return 'Company ' + (index + 1); } } - - public class AccountRandomizer implements TestModule.RecordRandomizer { - public AccountRandomizer() { - this.add(Account.Name, new NameRandomizer()); - this.add(Account.Industry, new IndustryRandomizer()); - } - } } ``` @@ -353,24 +357,21 @@ private class AccountServiceTest { .withName('Acme Enterprise') .buildAndInsert(); - System.assertEquals('Acme Enterprise', acc.Name); - System.assertEquals(1000000, acc.AnnualRevenue); + Assert.areEqual('Acme Enterprise', acc.Name); + Assert.areEqual(1000000, acc.AnnualRevenue); } @IsTest static void shouldCreateBulkAccounts() { - List accounts = AccountTestModule.Builder() + List accounts = AccountTestModule.Builder() .withAccountRandomizer() .buildAndInsert(50); - System.assertEquals(50, accounts.size()); + Assert.areEqual(50, accounts.size()); // Verify unique names - Set names = new Set(); - for (Account acc : accounts) { - names.add(acc.Name); - } - System.assertEquals(50, names.size()); + Assert.areEqual('Company 1', accounts[0].get('Name')); + Assert.areEqual('Company 2', accounts[1].get('Name')); } } ``` diff --git a/website/examples.md b/website/examples.md index 0ac6bb8..df79c45 100644 --- a/website/examples.md +++ b/website/examples.md @@ -1,6 +1,6 @@ # Examples -Practical examples using Test Lib. +Practical examples using Test Lib based on the actual codebase. ## Basic Builder Usage @@ -14,8 +14,8 @@ static void testCreateAccount() { .withIndustry('Technology') .buildAndInsert(); - System.assertNotEquals(null, acc.Id); - System.assertEquals('Acme Corporation', acc.Name); + Assert.isNotNull(acc.Id); + Assert.areEqual('Acme Corporation', acc.Name); } ``` @@ -24,13 +24,13 @@ static void testCreateAccount() { ```apex @IsTest static void testCreateMultipleAccounts() { - List accounts = AccountTestModule.Builder() + List accounts = AccountTestModule.Builder() .withIndustry('Technology') .buildAndInsert(5); - System.assertEquals(5, accounts.size()); - for (Account acc : accounts) { - System.assertEquals('Technology', acc.Industry); + Assert.areEqual(5, accounts.size()); + for (SObject acc : accounts) { + Assert.areEqual('Technology', acc.get('Industry')); } } ``` @@ -44,8 +44,8 @@ static void testEnterpriseAccount() { .enterprise() .buildAndInsert(); - System.assertEquals('Enterprise Account', acc.Name); - System.assertEquals(1000000, acc.AnnualRevenue); + Assert.areEqual('Enterprise Account', acc.Name); + Assert.areEqual(1000000, acc.AnnualRevenue); } @IsTest @@ -54,8 +54,23 @@ static void testStartupAccount() { .startup() .buildAndInsert(); - System.assertEquals('Startup Account', acc.Name); - System.assertEquals(100000, acc.AnnualRevenue); + Assert.areEqual('Startup Account', acc.Name); + Assert.areEqual(100000, acc.AnnualRevenue); +} +``` + +### Template with Override + +```apex +@IsTest +static void testTemplateOverride() { + Account acc = (Account) AccountTestModule.Builder() + .enterprise() + .withName('Custom Enterprise') + .buildAndInsert(); + + Assert.areEqual('Custom Enterprise', acc.Name); + Assert.areEqual(1000000, acc.AnnualRevenue); // From template } ``` @@ -66,15 +81,15 @@ static void testStartupAccount() { ```apex @IsTest static void testRandomIndustry() { - List accounts = AccountTestModule.Builder() + List accounts = AccountTestModule.Builder() .withRandomIndustry() - .buildAndInsert(4); + .build(4); // Industries cycle through: Technology, Finance, Healthcare, Retail - System.assertEquals('Technology', accounts[0].Industry); - System.assertEquals('Finance', accounts[1].Industry); - System.assertEquals('Healthcare', accounts[2].Industry); - System.assertEquals('Retail', accounts[3].Industry); + Assert.areEqual('Technology', accounts[0].get('Industry')); + Assert.areEqual('Finance', accounts[1].get('Industry')); + Assert.areEqual('Healthcare', accounts[2].get('Industry')); + Assert.areEqual('Retail', accounts[3].get('Industry')); } ``` @@ -83,35 +98,49 @@ static void testRandomIndustry() { ```apex @IsTest static void testRecordRandomizer() { - List accounts = AccountTestModule.Builder() + List accounts = AccountTestModule.Builder() .withAccountRandomizer() - .buildAndInsert(3); + .build(3); - // Each account has unique name and industry - System.assertEquals('Company 1', accounts[0].Name); - System.assertEquals('Company 2', accounts[1].Name); - System.assertEquals('Company 3', accounts[2].Name); + // Each account has unique name and cycling industry + Assert.areEqual('Company 1', accounts[0].get('Name')); + Assert.areEqual('Company 2', accounts[1].get('Name')); + Assert.areEqual('Company 3', accounts[2].get('Name')); } ``` -### Custom Randomizer +### Using ListRandomizer ```apex -public class EmailRandomizer implements TestModule.FieldRandomizer { - public Object generate(Integer index) { - return 'user' + (index + 1) + '@example.com'; - } +@IsTest +static void testListRandomizer() { + List accounts = AccountTestModule.Builder() + .withRandomizer(Account.Industry, + TestModule.ListRandomizer(new List{ 'Tech', 'Finance', 'Health' })) + .build(5); + + Assert.areEqual('Tech', accounts[0].get('Industry')); + Assert.areEqual('Finance', accounts[1].get('Industry')); + Assert.areEqual('Health', accounts[2].get('Industry')); + Assert.areEqual('Tech', accounts[3].get('Industry')); // Cycles back + Assert.areEqual('Finance', accounts[4].get('Industry')); } +``` -@IsTest -static void testCustomRandomizer() { - List contacts = ContactTestModule.Builder() - .withRandomizer(Contact.Email, new EmailRandomizer()) - .buildAndInsert(3); +### Combining Randomizers - System.assertEquals('user1@example.com', contacts[0].Email); - System.assertEquals('user2@example.com', contacts[1].Email); - System.assertEquals('user3@example.com', contacts[2].Email); +```apex +@IsTest +static void testCombinedRandomizers() { + List accounts = AccountTestModule.Builder() + .withRandomizer(new AccountRandomizer()) + .withRandomizer(Account.Industry, + TestModule.ListRandomizer(new List{ 'Override' })) + .build(2); + + // Name from AccountRandomizer, Industry overridden + Assert.areEqual('Company 1', accounts[0].get('Name')); + Assert.areEqual('Override', accounts[0].get('Industry')); } ``` @@ -127,8 +156,8 @@ static void testMockAccount() { .build(); // No database operation - acc.Id is null - System.assertEquals(null, acc.Id); - System.assertEquals('Mock Account', acc.Name); + Assert.isNull(acc.Id); + Assert.areEqual('Mock Account', acc.Name); } ``` @@ -142,8 +171,17 @@ static void testMockWithFakeId() { .build(); // Has a valid-looking ID without database insert - System.assertNotEquals(null, acc.Id); - System.assert(String.valueOf(acc.Id).startsWith('001')); + Assert.isNotNull(acc.Id); + Assert.isTrue(String.valueOf(acc.Id).startsWith('001')); +} + +@IsTest +static void testUniqueFakeIds() { + Account acc1 = (Account) AccountTestModule.Mocker().setFakeId().build(); + Account acc2 = (Account) AccountTestModule.Mocker().setFakeId().build(); + + // Each call generates unique ID + Assert.areNotEqual(acc1.Id, acc2.Id); } ``` @@ -157,7 +195,20 @@ static void testMockParentRelationship() { .build(); // Parent relationship is populated - System.assertEquals('Parent Corporation', acc.Parent.Name); + Assert.areEqual('Parent Corporation', acc.Parent.Name); +} +``` + +### Deeply Nested Parent + +```apex +@IsTest +static void testDeeplyNestedParent() { + Contact con = (Contact) ContactTestModule.Mocker() + .set('Account.Parent.Name', 'Grandparent Account') + .build(); + + Assert.areEqual('Grandparent Account', con.Account.Parent.Name); } ``` @@ -166,16 +217,37 @@ static void testMockParentRelationship() { ```apex @IsTest static void testMockChildRelationship() { - List contacts = ContactTestModule.Mocker() - .set(Contact.FirstName, 'John') - .build(3); + List contacts = new List{ + (Contact) ContactTestModule.Mocker().withLastName('Smith').build(), + (Contact) ContactTestModule.Mocker().withLastName('Jones').build() + }; Account acc = (Account) AccountTestModule.Mocker() .withContacts(contacts) .build(); // Child relationship is populated - System.assertEquals(3, acc.Contacts.size()); + Assert.areEqual(2, acc.Contacts.size()); + Assert.areEqual('Smith', acc.Contacts[0].LastName); + Assert.areEqual('Jones', acc.Contacts[1].LastName); +} +``` + +### Mock Read-Only Fields + +```apex +@IsTest +static void testMockReadOnlyFields() { + Datetime createdDate = Datetime.newInstance(2025, 1, 15, 10, 30, 0); + + Account acc = (Account) AccountTestModule.Mocker() + .setFakeId() + .set('CreatedDate', createdDate) + .set('Owner.Name', 'System Admin') + .build(); + + Assert.areEqual(createdDate, acc.CreatedDate); + Assert.areEqual('System Admin', acc.Owner.Name); } ``` @@ -192,12 +264,12 @@ static void testAccountWithContacts() { .buildAndInsert(); // Create related Contacts - List contacts = ContactTestModule.Builder() - .withAccount(acc.Id) + List contacts = ContactTestModule.Builder() + .set(Contact.AccountId, acc.Id) .buildAndInsert(3); // Verify - System.assertEquals(3, [SELECT COUNT() FROM Contact WHERE AccountId = :acc.Id]); + Assert.areEqual(3, [SELECT COUNT() FROM Contact WHERE AccountId = :acc.Id]); } ``` @@ -211,212 +283,167 @@ static void testOpportunityWithAccount() { .buildAndInsert(); Opportunity opp = (Opportunity) OpportunityTestModule.Builder() - .withAccount(acc.Id) + .set(Opportunity.AccountId, acc.Id) .withAmount(50000) .withCloseDate(Date.today().addDays(30)) .buildAndInsert(); - System.assertEquals(acc.Id, opp.AccountId); + Assert.areEqual(acc.Id, opp.AccountId); } ``` -## Unit Testing with Mocker +## Contact Test Module -### Testing Service Logic +### Using Contact Builder ```apex @IsTest -static void testCalculateExpectedRevenue() { - // Create mock opportunity without database - Opportunity opp = (Opportunity) OpportunityTestModule.Mocker() - .setFakeId() - .set(Opportunity.Amount, 100000) - .set(Opportunity.Probability, 80) - .build(); - - // Test pure business logic - Decimal expected = RevenueCalculator.calculateExpected(opp); +static void testContactBuilder() { + Contact con = (Contact) ContactTestModule.Builder() + .withFirstName('John') + .withLastName('Doe') + .withEmail('john.doe@example.com') + .buildAndInsert(); - System.assertEquals(80000, expected); + Assert.areEqual('John', con.FirstName); + Assert.areEqual('Doe', con.LastName); } ``` -### Testing Trigger Logic +### Contact Templates ```apex @IsTest -static void testAccountTriggerLogic() { - // Create mock account - Account oldAcc = (Account) AccountTestModule.Mocker() - .setFakeId() - .set(Account.Rating, 'Cold') - .build(); - - Account newAcc = (Account) AccountTestModule.Mocker() - .set(Account.Id, oldAcc.Id) - .set(Account.Rating, 'Hot') - .build(); +static void testContactTemplates() { + Contact business = (Contact) ContactTestModule.Builder() + .business() + .buildAndInsert(); - // Test trigger logic - Boolean ratingChanged = AccountTriggerHandler.hasRatingChanged(oldAcc, newAcc); + Contact personal = (Contact) ContactTestModule.Builder() + .personal() + .buildAndInsert(); - System.assert(ratingChanged); + Assert.areEqual('Business', business.FirstName); + Assert.areEqual('Personal', personal.FirstName); } ``` -### Testing Selector Results +### Contact Mocker ```apex @IsTest -static void testProcessQueryResults() { - // Mock query results with relationships - List contacts = ContactTestModule.Mocker().build(2); - - Account acc = (Account) AccountTestModule.Mocker() - .setFakeId() - .withContacts(contacts) - .withParentName('Holding Company') +static void testContactMocker() { + Contact con = (Contact) ContactTestModule.Mocker() + .withFakeId() + .withAccountName('Parent Account') .build(); - // Test processing logic - AccountDTO dto = AccountMapper.toDTO(acc); - - System.assertEquals(2, dto.contactCount); - System.assertEquals('Holding Company', dto.parentName); + Assert.isNotNull(con.Id); + Assert.areEqual('Parent Account', con.Account.Name); } ``` -## Template Implementation +## Opportunity Test Module -### Defining Templates +### Using Opportunity Builder ```apex -public class Templates implements TestModule.Template { - public SObject defaultTemplate() { - return new Account( - Name = 'Test Account', - Industry = 'Technology' - ); - } +@IsTest +static void testOpportunityBuilder() { + Opportunity opp = (Opportunity) OpportunityTestModule.Builder() + .withName('Big Deal') + .withStageName('Qualification') + .withAmount(500000) + .withCloseDate(Date.today().addDays(60)) + .buildAndInsert(); - public Map templates() { - return new Map{ - 'enterprise' => new Account( - Name = 'Enterprise Account', - Industry = 'Technology', - AnnualRevenue = 1000000, - NumberOfEmployees = 500 - ), - 'startup' => new Account( - Name = 'Startup Account', - Industry = 'Technology', - AnnualRevenue = 100000, - NumberOfEmployees = 10 - ), - 'partner' => new Account( - Name = 'Partner Account', - Type = 'Partner', - Industry = 'Consulting' - ) - }; - } + Assert.areEqual('Big Deal', opp.Name); + Assert.areEqual(500000, opp.Amount); } ``` -### Using Templates +### Opportunity Templates ```apex @IsTest -static void testTemplates() { - Account enterprise = (Account) AccountTestModule.Builder() - .enterprise() +static void testOpportunityTemplates() { + Opportunity prospecting = (Opportunity) OpportunityTestModule.Builder() + .prospecting() .buildAndInsert(); - Account startup = (Account) AccountTestModule.Builder() - .startup() + Opportunity closedWon = (Opportunity) OpportunityTestModule.Builder() + .closedWon() .buildAndInsert(); - System.assertEquals(1000000, enterprise.AnnualRevenue); - System.assertEquals(100000, startup.AnnualRevenue); + Assert.areEqual('Prospecting', prospecting.StageName); + Assert.areEqual('Closed Won', closedWon.StageName); + Assert.areEqual(100000, closedWon.Amount); } ``` -## Complete Test Module Example +### Opportunity Mocker ```apex @IsTest -public class ContactTestModule implements TestModule.BuilderProvider, TestModule.MockerProvider { +static void testOpportunityMocker() { + Opportunity opp = (Opportunity) OpportunityTestModule.Mocker() + .withFakeId() + .withAccountName('Customer Account') + .withAmount(75000) + .build(); - public static ContactBuilder Builder() { - return new ContactBuilder(); - } + Assert.isNotNull(opp.Id); + Assert.areEqual('Customer Account', opp.Account.Name); + Assert.areEqual(75000, opp.Amount); +} +``` - public static ContactMocker Mocker() { - return new ContactMocker(); - } +## Builder + Mocker Integration - public class ContactBuilder extends TestModule.RecordBuilder { - public ContactBuilder() { - super(new Templates()); - } - - public ContactBuilder withFirstName(String firstName) { - super.set(Contact.FirstName, firstName); - return this; - } - - public ContactBuilder withLastName(String lastName) { - super.set(Contact.LastName, lastName); - return this; - } - - public ContactBuilder withAccount(Id accountId) { - super.set(Contact.AccountId, accountId); - return this; - } - - public ContactBuilder withEmail(String email) { - super.set(Contact.Email, email); - return this; - } - - public ContactBuilder executive() { - super.useTemplate('executive'); - return this; - } - } +```apex +@IsTest +static void testBuilderThenMocker() { + // Create real account in database + Account realAccount = (Account) AccountTestModule.Builder() + .withName('Real Account') + .buildAndInsert(); - public class ContactMocker extends TestModule.RecordMocker { - public ContactMocker() { - super(new Templates()); - } + // Create mock contact referencing real account's name + Contact mockContact = (Contact) ContactTestModule.Mocker() + .withFakeId() + .set('Account.Name', realAccount.Name) + .build(); - public ContactMocker withAccountName(String accountName) { - super.set('Account.Name', accountName); - return this; - } - } + Assert.isNotNull(realAccount.Id); + Assert.isNotNull(mockContact.Id); + Assert.areEqual('Real Account', mockContact.Account.Name); +} +``` - public class Templates implements TestModule.Template { - public SObject defaultTemplate() { - return new Contact( - FirstName = 'John', - LastName = 'Doe', - Email = 'john.doe@example.com' - ); - } - - public Map templates() { - return new Map{ - 'executive' => new Contact( - FirstName = 'Jane', - LastName = 'Smith', - Title = 'CEO', - Email = 'jane.smith@example.com' - ) - }; - } - } +## Static Utilities + +### Using IdGenerator + +```apex +@IsTest +static void testIdGenerator() { + Id accountId = TestModule.IdGenerator.get(Account.SObjectType); + Id contactId = TestModule.IdGenerator.get(Contact.SObjectType); + + Assert.isTrue(String.valueOf(accountId).startsWith('001')); + Assert.isTrue(String.valueOf(contactId).startsWith('003')); + Assert.areNotEqual(accountId, contactId); +} +``` + +### Using fakeId() + +```apex +@IsTest +static void testFakeId() { + Id fakeId = TestModule.fakeId(Account.SObjectType); + + Assert.isTrue(String.valueOf(fakeId).startsWith('001')); } ``` diff --git a/website/getting-started.md b/website/getting-started.md index 27ce4ee..c6e581e 100644 --- a/website/getting-started.md +++ b/website/getting-started.md @@ -50,12 +50,12 @@ static void testWithRealRecords() { .buildAndInsert(); // Create multiple records - List accounts = AccountTestModule.Builder() + List accounts = AccountTestModule.Builder() .withAccountRandomizer() .buildAndInsert(10); // Records have real IDs and are in the database - System.assertNotEquals(null, acc.Id); + Assert.isNotNull(acc.Id); } ``` @@ -77,7 +77,7 @@ static void testWithMockedRecords() { .build(); // Mock child relationships - List contacts = ContactTestModule.Mocker().build(3); + List contacts = (List) ContactTestModule.Mocker().build(3); Account accWithContacts = (Account) AccountTestModule.Mocker() .withContacts(contacts) .build(); @@ -125,25 +125,28 @@ public class AccountMocker extends TestModule.RecordMocker { | Method | Description | |--------|-------------| -| `set(field, value)` | Set field value using SObjectField token | -| `set(fieldName, value)` | Set field value using String field name | -| `useTemplate(name)` | Apply named template | -| `withRandomizer(randomizer)` | Apply record randomizer | +| `set(SObjectField, Object)` | Set field value using SObjectField token | +| `set(String, Object)` | Set field value using String field name | +| `useTemplate(String)` | Apply named template | +| `withRandomizer(RecordRandomizer)` | Apply record randomizer for multiple fields | +| `withRandomizer(SObjectField, FieldRandomizer)` | Apply single field randomizer | | `build()` | Build single record (no DML) | | `buildAndInsert()` | Build and insert single record | -| `build(amount)` | Build multiple records (no DML) | -| `buildAndInsert(amount)` | Build and insert multiple records | +| `build(Integer)` | Build multiple records (no DML) | +| `buildAndInsert(Integer)` | Build and insert multiple records | ## Mocker Methods | Method | Description | |--------|-------------| -| `set(field, value)` | Set field value | -| `setChildren(relationship, records)` | Set child relationship | +| `set(SObjectField, Object)` | Set field value | +| `set(String, Object)` | Set field value (supports dot notation for parent relationships) | +| `setChildren(String, List)` | Set child relationship | | `setFakeId()` | Generate fake ID | -| `withRandomizer(randomizer)` | Apply randomizer | +| `withRandomizer(RecordRandomizer)` | Apply record randomizer | +| `withRandomizer(SObjectField, FieldRandomizer)` | Apply single field randomizer | | `build()` | Build single mock record | -| `build(amount)` | Build multiple mock records | +| `build(Integer)` | Build multiple mock records | ## Quick Example @@ -160,29 +163,29 @@ private class OpportunityServiceTest { // Act Opportunity opp = (Opportunity) OpportunityTestModule.Builder() - .withAccount(acc.Id) + .set(Opportunity.AccountId, acc.Id) .withAmount(100000) .buildAndInsert(); // Assert - System.assertEquals(acc.Id, opp.AccountId); - System.assertEquals(100000, opp.Amount); + Assert.areEqual(acc.Id, opp.AccountId); + Assert.areEqual(100000, opp.Amount); } @IsTest - static void shouldCalculateExpectedRevenue() { + static void shouldProcessClosedOpportunity() { // Arrange - Use Mocker for pure unit tests Opportunity opp = (Opportunity) OpportunityTestModule.Mocker() - .setFakeId() - .set(Opportunity.Amount, 100000) - .set(Opportunity.Probability, 75) + .withFakeId() + .withStageName('Closed Won') + .withAmount(100000) .build(); // Act - Test pure logic without database - Decimal expected = OpportunityService.calculateExpected(opp); + Boolean isClosed = OpportunityService.isClosed(opp); // Assert - System.assertEquals(75000, expected); + Assert.isTrue(isClosed); } } ``` diff --git a/website/index.md b/website/index.md index b79f872..145c45d 100644 --- a/website/index.md +++ b/website/index.md @@ -27,7 +27,7 @@ features: - icon: 📋 title: Templates - details: Define reusable record templates for common scenarios like "enterprise account" or "startup account". + details: Define reusable record templates for common scenarios like "enterprise account" or "closedWon opportunity". - icon: 🎲 title: Randomizers @@ -79,7 +79,7 @@ static void testAccountProcessing() { .buildAndInsert(); Contact con = (Contact) ContactTestModule.Builder() - .withAccount(acc.Id) + .set(Contact.AccountId, acc.Id) .buildAndInsert(); // Test logic... @@ -104,7 +104,7 @@ Account acc = (Account) AccountTestModule.Builder() .buildAndInsert(); // Multiple records with unique values -List accounts = AccountTestModule.Builder() +List accounts = AccountTestModule.Builder() .withAccountRandomizer() .buildAndInsert(100); ``` @@ -125,9 +125,9 @@ Account acc = (Account) AccountTestModule.Mocker() .build(); // Mock with child records -List contacts = ContactTestModule.Mocker().build(3); +List contacts = ContactTestModule.Mocker().build(3); Account acc = (Account) AccountTestModule.Mocker() - .withContacts(contacts) + .withContacts((List) contacts) .build(); ``` @@ -145,30 +145,30 @@ private class OpportunityServiceTest { .buildAndInsert(); Opportunity opp = (Opportunity) OpportunityTestModule.Builder() - .withAccount(acc.Id) + .set(Opportunity.AccountId, acc.Id) .withAmount(100000) - .withProbability(75) + .closedWon() .buildAndInsert(); // Act Decimal revenue = OpportunityService.calculateExpectedRevenue(opp.Id); // Assert - Assert.areEqual(75000, revenue); + Assert.areEqual(100000, revenue); } @IsTest static void shouldProcessOpportunityWithMockedData() { // Arrange - Use Mocker for unit tests (no DML) Opportunity opp = (Opportunity) OpportunityTestModule.Mocker() - .setFakeId() - .set(Opportunity.Amount, 100000) - .set(Opportunity.Probability, 75) + .withFakeId() + .withAmount(100000) + .withStageName('Closed Won') .build(); // Act & Assert - Test pure logic without database - Decimal expected = OpportunityService.calculateExpected(opp); - Assert.areEqual(75000, expected); + Boolean isClosed = OpportunityService.isClosed(opp); + Assert.isTrue(isClosed); } } ``` @@ -179,10 +179,10 @@ private class OpportunityServiceTest { - **Mocker Pattern** - Create in-memory records without DML - **Templates** - Reusable record configurations - **Randomizers** - Generate unique field values for bulk creation -- **Type Safety** - Compile-time field validation +- **Type Safety** - Compile-time field validation with SObjectField tokens - **Parent Relations** - Mock parent lookups (e.g., `Account.Parent.Name`) - **Child Relations** - Mock child collections (e.g., `Account.Contacts`) -- **Fake IDs** - Generate valid-looking IDs without database +- **Fake IDs** - Generate valid-looking IDs via `TestModule.IdGenerator` ## Part of Beyond The Cloud diff --git a/website/installation.md b/website/installation.md index c70c3a7..8db83b7 100644 --- a/website/installation.md +++ b/website/installation.md @@ -21,7 +21,7 @@ If you only want the core framework: ```bash sf project deploy start \ - --source-dir force-app/main/default/classes/test-module \ + --source-dir force-app/main/default/classes \ --target-org your-org-alias ``` @@ -31,20 +31,9 @@ sf project deploy start \ 1. Navigate to **Setup** → **Apex Classes** 2. Click **New** -3. Copy the code from [TestModule.cls](https://github.com/beyond-the-cloud-dev/test-lib/blob/main/force-app/main/default/classes/test-module/TestModule.cls) +3. Copy the code from [TestModule.cls](https://github.com/beyond-the-cloud-dev/test-lib/blob/main/force-app/main/default/classes/TestModule.cls) 4. Save the class -## Package Installation - -### Unlocked Package (Recommended) - -```bash -sf package install \ - --package "Test Lib@1.0.0" \ - --target-org your-org-alias \ - --wait 10 -``` - ## Dependencies Test Lib has **zero code dependencies**. It's a pure Apex library with no external requirements. @@ -62,32 +51,55 @@ Test the installation by creating a simple test: private class TestLibVerification { @IsTest - static void shouldCreateFakeId() { + static void shouldGenerateFakeId() { // Test fakeId generation - Id fakeAccountId = TestModule.fakeId(Account.SObjectType); + Id fakeAccountId = TestModule.IdGenerator.get(Account.SObjectType); - System.assertNotEquals(null, fakeAccountId); - System.assertEquals('001', String.valueOf(fakeAccountId).substring(0, 3)); + Assert.isNotNull(fakeAccountId); + Assert.isTrue(String.valueOf(fakeAccountId).startsWith('001')); + } + + @IsTest + static void shouldBuildAccount() { + // Test basic builder + Account acc = (Account) AccountTestModule.Builder() + .withName('Test Account') + .build(); + + Assert.areEqual('Test Account', acc.Name); + } + + @IsTest + static void shouldMockAccount() { + // Test basic mocker + Account acc = (Account) AccountTestModule.Mocker() + .setFakeId() + .build(); + + Assert.isNotNull(acc.Id); } } ``` -If the test passes, you're all set! +If the tests pass, you're all set! ## Project Structure After installation, you'll have: ``` -force-app/main/default/classes/test-module/ +force-app/main/default/classes/ ├── TestModule.cls # Core framework ├── TestModule.cls-meta.xml ├── TestModule_Test.cls # Framework tests ├── TestModule_Test.cls-meta.xml └── concrete-modules/ # Example implementations ├── AccountTestModule.cls + ├── AccountTestModule.cls-meta.xml ├── ContactTestModule.cls - └── OpportunityTestModule.cls + ├── ContactTestModule.cls-meta.xml + ├── OpportunityTestModule.cls + └── OpportunityTestModule.cls-meta.xml ``` ## Creating Your First Module @@ -102,6 +114,10 @@ public class AccountTestModule { return new AccountBuilder(); } + public static AccountMocker Mocker() { + return new AccountMocker(); + } + public class AccountBuilder extends TestModule.RecordBuilder { public AccountBuilder() { super(new Account(Name = 'Test Account')); @@ -112,6 +128,12 @@ public class AccountTestModule { return this; } } + + public class AccountMocker extends TestModule.RecordMocker { + public AccountMocker() { + super(new Account(Name = 'Test Account')); + } + } } ``` diff --git a/website/mocker.md b/website/mocker.md index ab45233..f46b5e7 100644 --- a/website/mocker.md +++ b/website/mocker.md @@ -33,19 +33,19 @@ Account acc = (Account) AccountTestModule.Mocker() .build(); // No database operation - acc.Id is null -System.assertEquals(null, acc.Id); -System.assertEquals('Mock Account', acc.Name); +Assert.isNull(acc.Id); +Assert.areEqual('Mock Account', acc.Name); ``` ### Multiple Records ```apex -List accounts = AccountTestModule.Mocker() +List accounts = AccountTestModule.Mocker() .set(Account.Industry, 'Technology') .build(10); // 10 in-memory records -System.assertEquals(10, accounts.size()); +Assert.areEqual(10, accounts.size()); ``` ## Setting Field Values @@ -82,25 +82,32 @@ Account acc = (Account) AccountTestModule.Mocker() .build(); // Has a valid ID format: 001000000000001 -System.assertNotEquals(null, acc.Id); -System.assert(String.valueOf(acc.Id).startsWith('001')); +Assert.isNotNull(acc.Id); +Assert.isTrue(String.valueOf(acc.Id).startsWith('001')); ``` -### Multiple Records with Fake IDs +### Unique IDs Across Calls -Each record gets a unique fake ID: +Each call to `setFakeId()` generates a unique ID: ```apex -List accounts = AccountTestModule.Mocker() - .setFakeId() - .build(3); +Account acc1 = (Account) AccountTestModule.Mocker().setFakeId().build(); +Account acc2 = (Account) AccountTestModule.Mocker().setFakeId().build(); -// 001000000000001, 001000000000002, 001000000000003 -Set ids = new Set(); -for (Account acc : accounts) { - ids.add(acc.Id); -} -System.assertEquals(3, ids.size()); +// Different IDs +Assert.areNotEqual(acc1.Id, acc2.Id); +``` + +### Static IdGenerator + +You can also use the static utility directly: + +```apex +Id accountId = TestModule.IdGenerator.get(Account.SObjectType); +Id contactId = TestModule.IdGenerator.get(Contact.SObjectType); + +Assert.isTrue(String.valueOf(accountId).startsWith('001')); +Assert.isTrue(String.valueOf(contactId).startsWith('003')); ``` ## Parent Relationships @@ -113,7 +120,7 @@ Account acc = (Account) AccountTestModule.Mocker() .build(); // Parent relationship is populated -System.assertEquals('Parent Corporation', acc.Parent.Name); +Assert.areEqual('Parent Corporation', acc.Parent.Name); ``` ### Implementation @@ -123,12 +130,6 @@ public AccountMocker withParentName(String name) { super.set('Parent.Name', name); return this; } - -public AccountMocker withOwner(String ownerName, String ownerEmail) { - super.set('Owner.Name', ownerName); - super.set('Owner.Email', ownerEmail); - return this; -} ``` ### Deep Nesting @@ -139,8 +140,22 @@ Contact con = (Contact) ContactTestModule.Mocker() .set('Account.Parent.Name', 'Acme Holdings') .build(); -System.assertEquals('Acme Corp', con.Account.Name); -System.assertEquals('Acme Holdings', con.Account.Parent.Name); +Assert.areEqual('Acme Corp', con.Account.Name); +Assert.areEqual('Acme Holdings', con.Account.Parent.Name); +``` + +### Read-Only Fields + +You can set read-only fields like `CreatedDate`, `Owner.Name`, etc.: + +```apex +Account acc = (Account) AccountTestModule.Mocker() + .setFakeId() + .set('CreatedDate', Datetime.newInstance(2025, 1, 15, 10, 30, 0)) + .set('Owner.Name', 'System Admin') + .build(); + +Assert.areEqual('System Admin', acc.Owner.Name); ``` ## Child Relationships @@ -148,8 +163,8 @@ System.assertEquals('Acme Holdings', con.Account.Parent.Name); Mock child record collections: ```apex -List contacts = ContactTestModule.Mocker() - .set(Contact.FirstName, 'Test') +List contacts = (List) ContactTestModule.Mocker() + .withLastName('Smith') .build(3); Account acc = (Account) AccountTestModule.Mocker() @@ -157,7 +172,7 @@ Account acc = (Account) AccountTestModule.Mocker() .build(); // Child records are accessible -System.assertEquals(3, acc.Contacts.size()); +Assert.areEqual(3, acc.Contacts.size()); ``` ### Implementation @@ -167,42 +182,48 @@ public AccountMocker withContacts(List contacts) { super.setChildren('Contacts', contacts); return this; } - -public AccountMocker withOpportunities(List opportunities) { - super.setChildren('Opportunities', opportunities); - return this; -} ``` ### Multiple Child Relationships ```apex -List contacts = ContactTestModule.Mocker().build(5); -List opps = OpportunityTestModule.Mocker().build(3); -List cases = CaseTestModule.Mocker().build(2); +List contacts = (List) ContactTestModule.Mocker().build(5); +List opps = (List) OpportunityTestModule.Mocker().build(3); Account acc = (Account) AccountTestModule.Mocker() .setFakeId() .withContacts(contacts) - .withOpportunities(opps) - .setChildren('Cases', cases) + .setChildren('Opportunities', opps) .build(); -System.assertEquals(5, acc.Contacts.size()); -System.assertEquals(3, acc.Opportunities.size()); -System.assertEquals(2, acc.Cases.size()); +Assert.areEqual(5, acc.Contacts.size()); +Assert.areEqual(3, acc.Opportunities.size()); ``` ## Randomizers with Mocker ```apex -List accounts = AccountTestModule.Mocker() +List accounts = AccountTestModule.Mocker() .withRandomIndustry() .build(4); // Industries cycle: Technology, Finance, Healthcare, Retail -System.assertEquals('Technology', accounts[0].Industry); -System.assertEquals('Finance', accounts[1].Industry); +Assert.areEqual('Technology', accounts[0].get('Industry')); +Assert.areEqual('Finance', accounts[1].get('Industry')); +``` + +### Using ListRandomizer + +```apex +List accounts = AccountTestModule.Mocker() + .withRandomizer(Account.Industry, + TestModule.ListRandomizer(new List{ 'A', 'B' })) + .build(4); + +Assert.areEqual('A', accounts[0].get('Industry')); +Assert.areEqual('B', accounts[1].get('Industry')); +Assert.areEqual('A', accounts[2].get('Industry')); +Assert.areEqual('B', accounts[3].get('Industry')); ``` ## Complete Mocker Example @@ -232,14 +253,15 @@ public class AccountTestModule implements TestModule.MockerProvider { return this; } - // Parent relationships - public AccountMocker withParentName(String parentName) { - super.set('Parent.Name', parentName); + // Fake ID + public AccountMocker withFakeId() { + super.setFakeId(); return this; } - public AccountMocker withOwnerName(String ownerName) { - super.set('Owner.Name', ownerName); + // Parent relationships + public AccountMocker withParentName(String parentName) { + super.set('Parent.Name', parentName); return this; } @@ -249,11 +271,6 @@ public class AccountTestModule implements TestModule.MockerProvider { return this; } - public AccountMocker withOpportunities(List opportunities) { - super.setChildren('Opportunities', opportunities); - return this; - } - // Randomizers public AccountMocker withRandomIndustry() { super.withRandomizer(Account.Industry, new IndustryRandomizer()); @@ -289,7 +306,7 @@ static void testDiscountCalculation() { // Test pure business logic Decimal discount = PricingService.calculateDiscount(acc); - System.assertEquals(0.15, discount); + Assert.areEqual(0.15, discount); } ``` @@ -311,7 +328,7 @@ static void testTriggerHandler() { // Test trigger logic without DML Boolean changed = AccountTriggerHandler.hasRatingChanged(oldAcc, newAcc); - System.assert(changed); + Assert.isTrue(changed); } ``` @@ -320,7 +337,7 @@ static void testTriggerHandler() { ```apex @IsTest static void testDTOMapping() { - List contacts = ContactTestModule.Mocker().build(3); + List contacts = (List) ContactTestModule.Mocker().build(3); Account acc = (Account) AccountTestModule.Mocker() .setFakeId() @@ -331,31 +348,29 @@ static void testDTOMapping() { // Test mapping logic AccountDTO dto = AccountMapper.toDTO(acc); - System.assertEquals(3, dto.contactCount); - System.assertEquals('Holding Company', dto.parentName); + Assert.areEqual(3, dto.contactCount); + Assert.areEqual('Holding Company', dto.parentName); } ``` -### Mocking Query Results +### Builder + Mocker Integration ```apex @IsTest -static void testServiceWithMockedQuery() { - // Create mock data as if from SOQL query - List contacts = ContactTestModule.Mocker() - .set(Contact.Email, 'test@example.com') - .build(5); - - Account acc = (Account) AccountTestModule.Mocker() - .setFakeId() - .set(Account.Name, 'Test Account') - .withContacts(contacts) +static void testBuilderThenMocker() { + // Create real account in database + Account realAccount = (Account) AccountTestModule.Builder() + .withName('Real Account') + .buildAndInsert(); + + // Create mock contact referencing real account + Contact mockContact = (Contact) ContactTestModule.Mocker() + .withFakeId() + .set('Account.Name', realAccount.Name) .build(); - // Test service that processes query results - Integer emailCount = AccountService.countContactEmails(acc); - - System.assertEquals(5, emailCount); + Assert.isNotNull(realAccount.Id); + Assert.areEqual('Real Account', mockContact.Account.Name); } ``` diff --git a/website/randomizers.md b/website/randomizers.md index 7fa2a35..9a3968c 100644 --- a/website/randomizers.md +++ b/website/randomizers.md @@ -26,7 +26,7 @@ The `index` parameter indicates which record is being generated (0-based). ### Basic Implementation ```apex -public class NameRandomizer implements TestModule.FieldRandomizer { +public class CompanyNameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { return 'Company ' + (index + 1); } @@ -51,85 +51,48 @@ public class IndustryRandomizer implements TestModule.FieldRandomizer { // Generates: Technology, Finance, Healthcare, Retail, Technology, ... ``` -## Randomizer Interface - -For generating multiple field values at once: - -```apex -public interface Randomizer { - Map generate(Integer index); -} -``` +## RecordRandomizer Interface -### Direct Implementation +For generating values for multiple fields at once: ```apex -public class AccountRandomizer implements TestModule.Randomizer { - public Map generate(Integer index) { - return new Map{ - Account.Name => 'Company ' + (index + 1), - Account.Industry => getIndustry(index), - Account.AnnualRevenue => (index + 1) * 100000 - }; - } - - private String getIndustry(Integer index) { - List industries = new List{ - 'Technology', 'Finance', 'Healthcare' - }; - return industries[Math.mod(index, industries.size())]; - } +public interface RecordRandomizer { + Map randomizers(); } ``` -## RecordRandomizer Class - -A helper class for composing multiple FieldRandomizers: +### Implementation ```apex public class AccountRandomizer implements TestModule.RecordRandomizer { - public AccountRandomizer() { - this.add(Account.Name, new NameRandomizer()); - this.add(Account.Industry, new IndustryRandomizer()); - this.add(Account.AnnualRevenue, new RevenueRandomizer()); - } -} -``` - -### Chaining Randomizers - -```apex -public class FullAccountRandomizer implements TestModule.RecordRandomizer { - public FullAccountRandomizer() { - // Base randomizer - this.setParent(new BasicAccountRandomizer()); - - // Additional fields - this.add(Account.Phone, new PhoneRandomizer()); - this.add(Account.Website, new WebsiteRandomizer()); + public Map randomizers() { + return new Map{ + Account.Name => new CompanyNameRandomizer(), + Account.Industry => new IndustryRandomizer() + }; } } ``` ## Built-in ListRandomizer -Cycles through a predefined list of values: +The `TestModule.ListRandomizer` cycles through a list of values: ```apex -new TestModule.ListRandomizer(new List{ - 'Technology', 'Finance', 'Healthcare', 'Retail' -}) +// Using static factory method +TestModule.ListRandomizer(new List{ 'Technology', 'Finance', 'Healthcare' }) // Usage -AccountTestModule.Builder() +List accounts = AccountTestModule.Builder() .withRandomizer(Account.Industry, - new TestModule.ListRandomizer(new List{ - 'Technology', 'Finance', 'Healthcare' - }) - ) + TestModule.ListRandomizer(new List{ 'Tech', 'Finance', 'Health' })) .build(6); -// Generates: Technology, Finance, Healthcare, Technology, Finance, Healthcare +// Generates: Tech, Finance, Health, Tech, Finance, Health +Assert.areEqual('Tech', accounts[0].get('Industry')); +Assert.areEqual('Finance', accounts[1].get('Industry')); +Assert.areEqual('Health', accounts[2].get('Industry')); +Assert.areEqual('Tech', accounts[3].get('Industry')); ``` ## Using Randomizers @@ -151,7 +114,7 @@ public class AccountBuilder extends TestModule.RecordBuilder { } // Usage -List accounts = AccountTestModule.Builder() +List accounts = AccountTestModule.Builder() .withAccountRandomizer() .buildAndInsert(100); ``` @@ -168,50 +131,46 @@ public class AccountMocker extends TestModule.RecordMocker { } // Usage -List accounts = AccountTestModule.Mocker() +List accounts = AccountTestModule.Mocker() .withRandomIndustry() .build(10); ``` -## Common Randomizer Patterns +### Combining Randomizers -### Sequential Names +You can combine a RecordRandomizer with additional FieldRandomizers: ```apex -public class CompanyNameRandomizer implements TestModule.FieldRandomizer { - private String prefix; +List accounts = AccountTestModule.Builder() + .withRandomizer(new AccountRandomizer()) + .withRandomizer(Account.Industry, + TestModule.ListRandomizer(new List{ 'Override' })) + .build(2); - public CompanyNameRandomizer() { - this.prefix = 'Company'; - } +// FieldRandomizer overrides the RecordRandomizer for Industry +Assert.areEqual('Company 0', accounts[0].get('Name')); // From AccountRandomizer +Assert.areEqual('Override', accounts[0].get('Industry')); // From ListRandomizer +``` - public CompanyNameRandomizer(String prefix) { - this.prefix = prefix; - } +## Common Randomizer Patterns + +### Sequential Names +```apex +public class CompanyNameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { - return prefix + ' ' + (index + 1); + return 'Company ' + (index + 1); } } // Company 1, Company 2, Company 3... -// or with custom prefix: -// Acme 1, Acme 2, Acme 3... ``` ### Email Generator ```apex public class EmailRandomizer implements TestModule.FieldRandomizer { - private String domain; - - public EmailRandomizer() { - this.domain = 'example.com'; - } - - public EmailRandomizer(String domain) { - this.domain = domain; - } + private String domain = 'example.com'; public Object generate(Integer index) { return 'user' + (index + 1) + '@' + domain; @@ -221,120 +180,122 @@ public class EmailRandomizer implements TestModule.FieldRandomizer { // user1@example.com, user2@example.com... ``` -### Phone Number Generator +### Amount Generator ```apex -public class PhoneRandomizer implements TestModule.FieldRandomizer { +public class AmountRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { - String areaCode = String.valueOf(100 + Math.mod(index, 900)).leftPad(3, '0'); - String prefix = String.valueOf(100 + Math.mod(index * 7, 900)).leftPad(3, '0'); - String suffix = String.valueOf(1000 + index).leftPad(4, '0'); - return '(' + areaCode + ') ' + prefix + '-' + suffix; + return (index + 1) * 50000; } } -// (100) 100-1000, (101) 107-1001... +// 50000, 100000, 150000... ``` -### Date Generator +### First/Last Name Randomizers ```apex -public class CloseDateRandomizer implements TestModule.FieldRandomizer { - private Date baseDate; - private Integer dayIncrement; +public class FirstNameRandomizer implements TestModule.FieldRandomizer { + private List firstNames = new List{ + 'John', 'Jane', 'Bob', 'Alice' + }; - public CloseDateRandomizer() { - this.baseDate = Date.today(); - this.dayIncrement = 7; + public Object generate(Integer index) { + return firstNames[Math.mod(index, firstNames.size())]; } +} +public class LastNameRandomizer implements TestModule.FieldRandomizer { public Object generate(Integer index) { - return baseDate.addDays(index * dayIncrement); + return 'Contact ' + (index + 1); } } - -// Today, Today+7, Today+14, Today+21... ``` -### Amount Generator +## Complete Example ```apex -public class AmountRandomizer implements TestModule.FieldRandomizer { - private Decimal baseAmount; - private Decimal multiplier; +@IsTest +public class ContactTestModule implements TestModule.BuilderProvider, TestModule.MockerProvider { - public AmountRandomizer() { - this.baseAmount = 10000; - this.multiplier = 1.5; + public static ContactBuilder Builder() { + return new ContactBuilder(); } - public Object generate(Integer index) { - return baseAmount * Math.pow(multiplier, index); + public static ContactMocker Mocker() { + return new ContactMocker(); } -} -// 10000, 15000, 22500, 33750... -``` - -### Picklist Cycler - -```apex -public class StageRandomizer implements TestModule.FieldRandomizer { - private List stages = new List{ - 'Prospecting', - 'Qualification', - 'Needs Analysis', - 'Value Proposition', - 'Negotiation', - 'Closed Won' - }; + public class ContactBuilder extends TestModule.RecordBuilder { + public ContactBuilder() { + super(new Templates()); + } - public Object generate(Integer index) { - return stages[Math.mod(index, stages.size())]; + public ContactBuilder withContactRandomizer() { + super.withRandomizer(new ContactRandomizer()); + return this; + } } -} -``` -## Composite Randomizer Example + public class ContactMocker extends TestModule.RecordMocker { + public ContactMocker() { + super(new Contact(FirstName = 'Test', LastName = 'Contact')); + } -```apex -public class FullContactRandomizer implements TestModule.RecordRandomizer { - public FullContactRandomizer() { - this.add(Contact.FirstName, new FirstNameRandomizer()); - this.add(Contact.LastName, new LastNameRandomizer()); - this.add(Contact.Email, new EmailRandomizer()); - this.add(Contact.Phone, new PhoneRandomizer()); - this.add(Contact.Title, new TitleRandomizer()); + public ContactMocker withContactRandomizer() { + super.withRandomizer(new ContactRandomizer()); + return this; + } } -} -public class FirstNameRandomizer implements TestModule.FieldRandomizer { - private List names = new List{ - 'John', 'Jane', 'Bob', 'Alice', 'Charlie', 'Diana' - }; + public class Templates implements TestModule.Template { + public SObject defaultTemplate() { + return new Contact( + FirstName = 'Test', + LastName = 'Contact', + Email = 'test.contact@example.com' + ); + } + + public Map templates() { + return new Map{ + 'business' => new Contact( + FirstName = 'Business', + LastName = 'Contact', + Email = 'business.contact@example.com' + ), + 'personal' => new Contact( + FirstName = 'Personal', + LastName = 'Contact', + Email = 'personal.contact@example.com' + ) + }; + } + } - public Object generate(Integer index) { - return names[Math.mod(index, names.size())]; + public class ContactRandomizer implements TestModule.RecordRandomizer { + public Map randomizers() { + return new Map{ + Contact.FirstName => new FirstNameRandomizer(), + Contact.LastName => new LastNameRandomizer() + }; + } } -} -public class LastNameRandomizer implements TestModule.FieldRandomizer { - private List names = new List{ - 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia' - }; + public class FirstNameRandomizer implements TestModule.FieldRandomizer { + private List firstNames = new List{ + 'John', 'Jane', 'Bob', 'Alice' + }; - public Object generate(Integer index) { - return names[Math.mod(index, names.size())]; + public Object generate(Integer index) { + return firstNames[Math.mod(index, firstNames.size())]; + } } -} -public class TitleRandomizer implements TestModule.FieldRandomizer { - private List titles = new List{ - 'CEO', 'CTO', 'CFO', 'VP Sales', 'Director', 'Manager' - }; - - public Object generate(Integer index) { - return titles[Math.mod(index, titles.size())]; + public class LastNameRandomizer implements TestModule.FieldRandomizer { + public Object generate(Integer index) { + return 'Contact ' + (index + 1); + } } } ``` @@ -346,33 +307,13 @@ public class TitleRandomizer implements TestModule.FieldRandomizer { ```apex @IsTest static void testBulkAccounts() { - List accounts = AccountTestModule.Builder() + List accounts = AccountTestModule.Builder() .withAccountRandomizer() .buildAndInsert(100); - // Verify unique names - Set names = new Set(); - for (Account acc : accounts) { - names.add(acc.Name); - } - System.assertEquals(100, names.size()); -} -``` - -### Mixed Randomizers - -```apex -@IsTest -static void testMixedRandomizers() { - List accounts = AccountTestModule.Builder() - .enterprise() // Base template - .withRandomizer(Account.Name, // Override name - new CompanyNameRandomizer('Enterprise')) - .withRandomIndustry() // Random industry - .buildAndInsert(10); - - System.assertEquals('Enterprise 1', accounts[0].Name); - System.assertEquals('Enterprise 2', accounts[1].Name); + Assert.areEqual(100, accounts.size()); + Assert.areEqual('Company 1', accounts[0].get('Name')); + Assert.areEqual('Company 100', accounts[99].get('Name')); } ``` @@ -385,13 +326,13 @@ static void testRelatedRecords() { .enterprise() .buildAndInsert(); - List contacts = ContactTestModule.Builder() - .withAccount(acc.Id) - .withRandomizer(new FullContactRandomizer()) + List contacts = ContactTestModule.Builder() + .set(Contact.AccountId, acc.Id) + .withContactRandomizer() .buildAndInsert(5); - System.assertEquals(5, contacts.size()); - // Each contact has unique name, email, phone, title + Assert.areEqual(5, contacts.size()); + // John Contact 1, Jane Contact 2, Bob Contact 3, Alice Contact 4, John Contact 5 } ``` @@ -401,6 +342,6 @@ static void testRelatedRecords() { 2. **Use meaningful values** - Generate realistic data 3. **Consider uniqueness** - Ensure generated values don't cause duplicates 4. **Make reusable** - Create generic randomizers for common patterns -5. **Test your randomizers** - Verify they generate expected values +5. **Implement RecordRandomizer** - When you need to randomize multiple related fields [API Reference →](/api) diff --git a/website/templates.md b/website/templates.md index 62e5757..cf10b57 100644 --- a/website/templates.md +++ b/website/templates.md @@ -41,8 +41,7 @@ Returns a map of named templates: public Map templates() { return new Map{ 'enterprise' => new Account(...), - 'startup' => new Account(...), - 'partner' => new Account(...) + 'startup' => new Account(...) }; } ``` @@ -57,8 +56,7 @@ public class Templates implements TestModule.Template { public SObject defaultTemplate() { return new Account( Name = 'Test Account', - Industry = 'Technology', - BillingCountry = 'USA' + Industry = 'Technology' ); } @@ -67,22 +65,12 @@ public class Templates implements TestModule.Template { 'enterprise' => new Account( Name = 'Enterprise Account', Industry = 'Technology', - AnnualRevenue = 1000000, - NumberOfEmployees = 500, - Rating = 'Hot' + AnnualRevenue = 1000000 ), 'startup' => new Account( Name = 'Startup Account', Industry = 'Technology', - AnnualRevenue = 100000, - NumberOfEmployees = 10, - Rating = 'Warm' - ), - 'partner' => new Account( - Name = 'Partner Account', - Type = 'Partner', - Industry = 'Consulting', - Rating = 'Hot' + AnnualRevenue = 100000 ) }; } @@ -108,11 +96,6 @@ public class AccountBuilder extends TestModule.RecordBuilder { super.useTemplate('startup'); return this; } - - public AccountBuilder partner() { - super.useTemplate('partner'); - return this; - } } ``` @@ -125,9 +108,8 @@ Account acc = (Account) AccountTestModule.Builder() .enterprise() .buildAndInsert(); -System.assertEquals('Enterprise Account', acc.Name); -System.assertEquals(1000000, acc.AnnualRevenue); -System.assertEquals(500, acc.NumberOfEmployees); +Assert.areEqual('Enterprise Account', acc.Name); +Assert.areEqual(1000000, acc.AnnualRevenue); ``` ### Template with Overrides @@ -141,9 +123,9 @@ Account acc = (Account) AccountTestModule.Builder() .withIndustry('Finance') // Override industry .buildAndInsert(); -System.assertEquals('Custom Enterprise Name', acc.Name); -System.assertEquals('Finance', acc.Industry); -System.assertEquals(1000000, acc.AnnualRevenue); // From template +Assert.areEqual('Custom Enterprise Name', acc.Name); +Assert.areEqual('Finance', acc.Industry); +Assert.areEqual(1000000, acc.AnnualRevenue); // From template ``` ### Direct Template Access @@ -156,63 +138,76 @@ public AccountBuilder useCustomTemplate(String templateName) { // Usage AccountTestModule.Builder() - .useCustomTemplate('partner') + .useTemplate('startup') .buildAndInsert(); ``` -## Template Design Patterns +## Real-World Examples -### By Business Scenario +### Account Templates + +From `AccountTestModule.cls`: ```apex -public Map templates() { - return new Map{ - // Size-based - 'enterprise' => new Account(AnnualRevenue = 1000000, NumberOfEmployees = 500), - 'mid-market' => new Account(AnnualRevenue = 500000, NumberOfEmployees = 100), - 'startup' => new Account(AnnualRevenue = 100000, NumberOfEmployees = 10), - - // Type-based - 'customer' => new Account(Type = 'Customer', Rating = 'Hot'), - 'prospect' => new Account(Type = 'Prospect', Rating = 'Warm'), - 'partner' => new Account(Type = 'Partner') - }; +public class Templates implements TestModule.Template { + public SObject defaultTemplate() { + return new Account(Name = 'Test Account', Industry = 'Technology'); + } + + public Map templates() { + return new Map{ + 'enterprise' => new Account( + Name = 'Enterprise Account', + Industry = 'Technology', + AnnualRevenue = 1000000 + ), + 'startup' => new Account( + Name = 'Startup Account', + Industry = 'Technology', + AnnualRevenue = 100000 + ) + }; + } } ``` -### By Test Context +### Contact Templates + +From `ContactTestModule.cls`: ```apex -public Map templates() { - return new Map{ - // For integration tests - 'integration' => new Account( - Name = 'Integration Test Account', - BillingStreet = '123 Test St', - BillingCity = 'Test City', - BillingState = 'CA', - BillingPostalCode = '94105', - BillingCountry = 'USA' - ), - - // For validation tests - 'minimal' => new Account(Name = 'Minimal Account'), - - // For workflow tests - 'workflow-trigger' => new Account( - Name = 'Workflow Test', - Rating = 'Cold', - Industry = 'Technology' - ) - }; +public class Templates implements TestModule.Template { + public SObject defaultTemplate() { + return new Contact( + FirstName = 'Test', + LastName = 'Contact', + Email = 'test.contact@example.com' + ); + } + + public Map templates() { + return new Map{ + 'business' => new Contact( + FirstName = 'Business', + LastName = 'Contact', + Email = 'business.contact@example.com' + ), + 'personal' => new Contact( + FirstName = 'Personal', + LastName = 'Contact', + Email = 'personal.contact@example.com' + ) + }; + } } ``` ### Opportunity Templates -```apex -public class OpportunityTemplates implements TestModule.Template { +From `OpportunityTestModule.cls`: +```apex +public class Templates implements TestModule.Template { public SObject defaultTemplate() { return new Opportunity( Name = 'Test Opportunity', @@ -223,29 +218,16 @@ public class OpportunityTemplates implements TestModule.Template { public Map templates() { return new Map{ - 'won' => new Opportunity( - Name = 'Won Deal', - StageName = 'Closed Won', - CloseDate = Date.today(), - Probability = 100 + 'prospecting' => new Opportunity( + Name = 'Prospecting Opportunity', + StageName = 'Prospecting', + CloseDate = Date.today().addDays(30) ), - 'lost' => new Opportunity( - Name = 'Lost Deal', - StageName = 'Closed Lost', + 'closedWon' => new Opportunity( + Name = 'Closed Won Opportunity', + StageName = 'Closed Won', CloseDate = Date.today(), - Probability = 0 - ), - 'negotiation' => new Opportunity( - Name = 'In Negotiation', - StageName = 'Negotiation/Review', - CloseDate = Date.today().addDays(14), - Probability = 75 - ), - 'big-deal' => new Opportunity( - Name = 'Big Deal', - Amount = 500000, - StageName = 'Qualification', - CloseDate = Date.today().addDays(90) + Amount = 100000 ) }; } @@ -284,117 +266,37 @@ AccountTestModule.Builder() ```apex @IsTest static void testInvalidTemplate() { + Boolean exceptionThrown = false; try { AccountTestModule.Builder() .useTemplate('invalid') .build(); - System.assert(false, 'Should have thrown exception'); } catch (TestModule.TestModuleException e) { - System.assert(e.getMessage().contains('not found')); + exceptionThrown = true; + Assert.isTrue(e.getMessage().contains('not found')); } + Assert.isTrue(exceptionThrown); } ``` ## Templates with Mocker -Templates work the same way with Mocker: +Templates work the same way with Mocker when using the Template constructor: ```apex public class AccountMocker extends TestModule.RecordMocker { public AccountMocker() { - super(new Templates()); // Same templates - } -} - -// Usage -Account acc = (Account) AccountTestModule.Mocker() - .enterprise() - .setFakeId() - .build(); -``` - -## Complete Example - -```apex -@IsTest -public class AccountTestModule { - - public static AccountBuilder Builder() { - return new AccountBuilder(); - } - - public static AccountMocker Mocker() { - return new AccountMocker(); - } - - public class AccountBuilder extends TestModule.RecordBuilder { - public AccountBuilder() { - super(new Templates()); - } - - public AccountBuilder enterprise() { - super.useTemplate('enterprise'); - return this; - } - - public AccountBuilder startup() { - super.useTemplate('startup'); - return this; - } - - public AccountBuilder partner() { - super.useTemplate('partner'); - return this; - } - } - - public class AccountMocker extends TestModule.RecordMocker { - public AccountMocker() { - super(new Templates()); - } - - // Same template methods... - } - - public class Templates implements TestModule.Template { - public SObject defaultTemplate() { - return new Account( - Name = 'Test Account', - Industry = 'Technology' - ); - } - - public Map templates() { - return new Map{ - 'enterprise' => new Account( - Name = 'Enterprise Account', - Industry = 'Technology', - AnnualRevenue = 1000000, - NumberOfEmployees = 500 - ), - 'startup' => new Account( - Name = 'Startup Account', - Industry = 'Technology', - AnnualRevenue = 100000, - NumberOfEmployees = 10 - ), - 'partner' => new Account( - Name = 'Partner Account', - Type = 'Partner', - Industry = 'Consulting' - ) - }; - } + super(new Account(Name = 'Test Account', Industry = 'Technology')); } } ``` ## Best Practices -1. **Name templates clearly** - Use descriptive names like `enterprise`, `won`, `minimal` +1. **Name templates clearly** - Use descriptive names like `enterprise`, `closedWon`, `business` 2. **Keep templates focused** - Each template should represent one scenario 3. **Allow overrides** - Templates set defaults; allow users to override 4. **Document templates** - Comment what each template represents -5. **Share templates** - Use the same Templates class for Builder and Mocker +5. **Use consistent patterns** - Follow the same naming conventions across modules [Next: Randomizers →](/randomizers)