DML Mocking Patterns
Overview
DML mocking patterns make service logic testable without relying on heavy real-database setup in every test.
Use this approach when business logic complexity is high and test speed/stability matter.
Consensus Best Practices
- Keep business logic in services, persistence in repositories.
- Inject repository dependencies in constructors.
- Use in-memory mocks to verify interactions and payloads.
- Still keep integration tests that execute real DML for end-to-end validation.
Pattern 1: Repository Boundary
public interface IContactRepository {
void insertContacts(List<Contact> contacts);
void updateContacts(List<Contact> contacts);
}
public with sharing class ContactRepository implements IContactRepository {
public void insertContacts(List<Contact> contacts) { insert contacts; }
public void updateContacts(List<Contact> contacts) { update contacts; }
}
Pattern 2: Service with Injected Dependency
public with sharing class ContactService {
private IContactRepository repo;
public ContactService(IContactRepository repo) {
this.repo = repo;
}
public void processContacts(List<Contact> contacts) {
// validate + enrich
repo.insertContacts(contacts);
}
}
Pattern 3: In-Memory Test Double
@IsTest
private class InMemoryContactRepository implements IContactRepository {
public List<Contact> inserted = new List<Contact>();
public List<Contact> updated = new List<Contact>();
public void insertContacts(List<Contact> contacts) { inserted.addAll(contacts); }
public void updateContacts(List<Contact> contacts) { updated.addAll(contacts); }
}
Pattern 4: Interaction Assertions
@IsTest
private class ContactServiceTests {
@IsTest
static void processContacts_callsRepository() {
InMemoryContactRepository mockRepo = new InMemoryContactRepository();
ContactService service = new ContactService(mockRepo);
service.processContacts(new List<Contact>{ new Contact(LastName = 'Test') });
System.assertEquals(1, mockRepo.inserted.size());
}
}
Tradeoffs
- Extra abstraction adds small design overhead.
- Worth it when services have complex branching and many failure modes.
- For simple CRUD wrappers, direct DML tests may be enough.