Series Note: This article is Part 3 of our hands-on engineering series: Building the Salesforce Appointment Scheduler. Follow along as we design and implement a multi-location scheduling platform on Salesforce using enterprise patterns and developer-first tooling.
The Testing Debt Crisis in Salesforce
Testing in Salesforce often suffers from an architectural anti-pattern: the monolithic TestUtility class.
As a project grows, developers tack more methods onto a single 2,000-line test utility:
- Methods with 14 parameters just to create an Account.
- Unnecessary DML operations on every test method, burning through SOQL and DML governor limits before the business logic under test even begins.
- Scratch orgs that spin up completely empty, forcing developers to manually click through setup wizards or paste one-off anonymous Apex snippets just to test a component in the browser.
In Part 3 of our Salesforce Appointment Scheduler project, we solve these testing challenges right from the start using Hob: The Salesforce House-Elf (hob create factory and hob seed init).
The Pattern: Separation of In-Memory (build) and Database (create)
Enterprise Apex testing distinguishes between two categories of tests:
- Unit Tests (Fast, In-Memory): Testing business logic, availability algorithms, date calculations, or postal code validation. These tests should never perform DML. They construct sObjects in memory and execute in milliseconds.
- Integration Tests (Database): Testing triggers, SOQL queries, roll-up summaries, and platform events. These tests require actual database persistence.
Hob’s test factory generator embeds this pattern directly into every factory it scaffolds:
Unit Test / Caller
|
+---> buildAccount() --> In-Memory sObject (Fast, 0 DML, No Limits)
|
+---> createAccount() --> buildAccount() + DML insert (For SOQL / Triggers)
Every factory generated by Hob exposes symmetrical entry points:
build<SObject>(): Returns an in-memory sObject populated with sensible defaults (No DML).build<SObject>(fieldOverrides): In-memory sObject with custom field overrides.create<SObject>(): Inserts a single record into the database and returns it.create<SObject>(fieldOverrides): Inserts a record with custom field overrides.- Bulk equivalents:
build<SObject>List(count)andcreate<SObject>List(count).
Scaffolding the Factories with Hob
With six core entities in our domain model, we scaffolded dedicated factories for each in seconds:
hob create factory Account
hob create factory Contact
hob create factory Service_Type__c
hob create factory Staff_Service_Type__c
hob create factory Staff_Working_Hours__c
hob create factory Appointment__c
The Terminal Output
✨ Hob - The Salesforce House-Elf ✨
"Loyal, quiet, and tireless assistance for your Salesforce Development."
✔ Created Test Data Factory 'AppointmentDataFactory' for 'Appointment__c'
🪄 Hob crafted your Apex Test Data Factory with clean build vs create separation:
Class: AppointmentDataFactory.cls
Target Object:Appointment__c
Directory: force-app/main/default/classes
Sample Test Usage:
// Fast in-memory unit test record:
Appointment__c r1 = AppointmentDataFactory.buildAppointment();
// Integration test record with DML:
Appointment__c r2 = AppointmentDataFactory.createAppointment();
// Bulk insertion:
List<Appointment__c> list1 = AppointmentDataFactory.createAppointmentList(5);
Real-World Nuances & Domain Refinements
Hob gives you a production-ready architectural scaffold. To tailor these factories for the Appointment Scheduler, we enriched each class with domain intelligence:
1. Handling AutoNumber Fields (Appointment__c)
Appointment__c.Name is an AutoNumber field (APT-{0000}). In Apex, attempting to set Name = 'Test...' on an AutoNumber custom object causes a compilation or runtime error because the field is read-only.
We omitted Name assignments and pre-populated the required DateTime and Status fields:
public static Appointment__c buildAppointment(Map<String, Object> fieldOverrides) {
DateTime defaultStart = DateTime.now().addDays(1);
Appointment__c record = new Appointment__c(
Start_Time__c = defaultStart,
End_Time__c = defaultStart.addHours(1),
Status__c = 'Scheduled',
Notes__c = 'Test appointment notes ' + index
);
index++;
applyOverrides(record, fieldOverrides);
return record;
}
2. Multi-RecordType Awareness (ContactDataFactory & AccountDataFactory)
Our domain model relies on distinct Record Types:
Account→LocationContact→Customervs.Location_Staff
We enriched ContactDataFactory with specialized builder methods:
// Default build: Customer with home address
Contact customer = ContactDataFactory.buildContact();
// Staff builder: Pre-linked to branch Account
Contact staff = ContactDataFactory.buildStaffContact(branchAccountId, null);
3. Weekly Schedule Generation (Staff_Working_HoursDataFactory)
Testing slot availability algorithms requires realistic working hours across days of the week. We added a helper that seeds a full Monday-Friday shift schedule with one call:
// Generates Mon-Fri 09:00 - 17:00 shifts in one line
List<Staff_Working_Hours__c> weeklyShifts =
Staff_Working_HoursDataFactory.createWeeklySchedule(staffContact.Id);
Seeding Scratch Orgs: hob seed init
Unit tests handle automated testing, but developers also need rich demo data when opening a scratch org in the browser or validating LWC components.
Hob includes built-in seed management. Running hob seed init scaffolded our seed fixtures:
hob seed init
✔ Scaffolded starter test data fixtures:
scripts/apex/seed.apex
data/data-plan.json
data/Accounts.json
data/Contacts.json
We tailored scripts/apex/seed.apex to plant a complete, realistic Appointment Scheduler ecosystem:
- Nationwide Branch Coverage (London, Birmingham, Manchester, Leeds):
- London Flagship (
EC2V 6DN, Cheapside) - Birmingham Colmore (
B3 2QD, Colmore Row) - Manchester Deansgate (
M3 2BB, Deansgate) - Leeds Park Row (
LS1 5HD, Park Row)
All seeded with standardShippingAddresscompound geocodes (ShippingLatitudeandShippingLongitude) to enable cross-country SOQLDISTANCE()queries.
- London Flagship (
- Realistic Staffing (Full-Time & Part-Time Mix):
- Full-time specialists across all regional hubs (Alice in London, Bob in Birmingham, Charlie in Manchester, Fiona in Leeds) covering core weekday shifts.
- Part-time specialists with realistic shift models:
- Morning shifts: David in London (Mon/Wed/Fri 09:00–13:00) and George in Leeds (Wed–Fri 08:30–13:30).
- Afternoon shifts: Liam in Manchester (Mon–Wed 12:30–17:00).
- Specific working days: Priya in Birmingham (Tue/Thu 09:00–17:00).
- Regional Customers: Distributed strategically across the UK near each regional hub (London, Solihull/Birmingham, Salford/Manchester, Leeds) with genuine UK postal codes.
- Service Catalog & Tiered Qualifications: 30-min Consultation, 60-min Standard Service, and 120-min Comprehensive Inspection, mapped via junction records to technician skills.
- Regional Starter Appointments: Active upcoming bookings in London and Manchester to test slot conflict detection and distance matching.
Whenever a developer spins up a new scratch org, running hob seed or executing seed.apex immediately turns an empty org into a fully functional demo environment.
DX Verdict & Tool Feedback
What Shined
- Clean Architectural Default: Moving every object to its own
[SObject]DataFactorycompletely eliminates the monolithicTestUtilityanti-pattern. - The Override Map Pattern: The
applyOverrides(record, fieldOverrides)method provides total flexibility without needing dozens of constructor permutations. - Seed Scaffolding: Having
hob seed initgenerate both anonymous Apex and JSON data plan structures bridges the gap between unit test factories and developer demo environments.
Opportunities for Hob’s Backlog
- AutoNumber Field Detection: Hob could check if the sObject has an AutoNumber name field and omit
Name = ...in the generated template. - Template Variable Scoping: A minor template glitch where
baseNamewas referenced in the assignment without a corresponding variable declaration can be polished in Hob’s template engine.
The Appointment Scheduler Series
- Series Index: Building the Salesforce Appointment Scheduler: Architecture & Series Index
- ← Part 1: Eliminating Salesforce XML Fatigue — CLI Schema Scaffolding with Hob
- ← Part 2: Instant Least-Privilege Access — Generating Permission Sets with Hob
- Part 3: Standardizing Test Hygiene — In-Memory vs DML Factories with Hob (Current Article)
- Part 4: High-Velocity TDD — Companion Test Scaffolding with Hob →
- Part 5: Architecture by Default — Separation of Concerns with Hob Triggers
- Part 6: Building Modern Lightning UIs Fast — Scaffolded LWCs with Hob
- Part 7: One Command to Rule Them All — The Complete Dev Org Setup with Hob