If you plan to write solid unit tests in Salesforce, you won’t get around mocking sobjects. A solid testing strategy needs a powerful library to mock sobject records. This allows you to write loosely coupled tests without the need to insert records into the database.
There are a few projects out there, but most of them focus on mocking Apex classes with the Stub API. I did not find one that satisfied my needs in terms of usability and feature-richness for mocking SObject records. So I went out and wrote my own. Since there are way too few open source projects in the Salesforce ecosystem anyway, I published the library as Open Source under the Apache License.
Why Mock SObject Records On Salesforce?
Besides the advantage in speed (calls to the database are always slow, and get even slower the more complex your domain is), mocking records decouples your tests from the domain implementation. This greatly increases the stability of your tests and allows you to use input for your unit tests that would otherwise be impossible to fabricate. A mocking library should allow you to conveniently …
- set system fields like
CreatedDate
orId
- override formula-fields
- populate parent- and child relationships
- bypass all domain validation (validation rules, triggers, required fields)
- simulate legacy or corrupted data in tests
That being said, the library’s API should also be accessible, act as documentation for your test data, and be easy to understand.
Comparing js-apex-utils With Other Libraries
Here’s an overview of existing libraries I found that solve a similar problem:
- apex-enterprise-patterns/fflib-apex-mocks: Powerful features to stub apex classes, but very limited support for mocking sobject records.
- apexfarm/ApexTestKit: Fluent-API-style library to mock large data sets. Focusses on conveniently setting up bulk records with structured test data.
- neowit/apexmock: Convenient test data generation to insert records into the database. No support for mocking query results (e.g. parent- and child relationships or read-only fields).
Each project has a slightly different focus. Here’s a comparison regarding their ability to mock sobjects (omitting other features, so this is not complete. Check them out by yourself to get the full picture).
Feature | js-apex-utils | fflib-apex-mocks | ApexTestKit | apexmock |
---|---|---|---|---|
Create plain SObject mock record | ✅ | ✅ | ✅ | |
Mock records from JSON template | ✅ | |||
Create a complex mock record | ✅ | ✅ | ||
Generate mock Id for any sobject | ✅ | ✅ | ✅ | |
Mock Parent relationships | ✅ | ✅ | ||
Mock Child relationships (sub-selects) | ✅ | ✅ | ✅ | |
Override read-only fields (formulas, system fields) | ✅ | ✅ | ✅ | |
Bulk record setup (query result mocking) | ✅ | ✅ | ||
Resolve record types from developer names | ✅ | ✅ | ||
Validate dynamic input (field API names, etc) | ✅ |
Key Features of js-apex-utils
The aim of this library is to mock a sobject record as conveniently as possible. I believe that tests are the best developer documentation there is. So check out the test class to see the mock factory in action.
Create A SObject Mock Record
There are multiple ways to create a new mock instance of a sobject. Unless you specify the Id
in the input map, it is automatically populated for you.
The easiest way is to create a blank record with an auto-generated id:
Account mockAcc = (Account) TestMockFactory.createSObjectMock(Account.SObjectType);
You can specify input in a form of field/value mappings, too. You can set any system-, compound- or read-only field (such as formula fields). Field names are validated, so you can’t accidentally mistype:
Account mockAcc = (Account) TestMockFactory.createSObjectMock(
Account.SObjectType,
new Map<String, Object>{
'Name' => 'Company Ltd.',
'AccountNumber' => '1000',
'CreatedDate' => System.now(),
'BillingAddress' => new Map<String, Object>{
'street' => 'Test Straße 1',
'postalCode' => '86150',
'city' => 'Augsburg'
}
}
);
Generate Mock Id
In case you simply need a unique record id, use generateFakeId
. The generated Id always uses the correct prefix of the custom object (abc
). If you call the method without specifying the Id index, the counter is auto-incremented, so subsequent calls will yield 001000000000002AAA
, 001000000000003AAA
, etc.
// 001000000000001AAA
Id accIdOne = TestMockFactory.generateFakeId(Account.SObjectType);
// 001000000000002AAA
Id accIdTwo = TestMockFactory.generateFakeId(Account.SObjectType);
// 801000000000001AAA
Id orderId = TestMockFactory.generateFakeId(Order.SObjectType);
// abc000000000001AAA
Id customId = TestMockFactory.generateFakeId(MyCustomObject__c.SObjectType);
You can also set the index manually and generate a specific Id:
// 001000000000010AAA
Id accId = TestMockFactory.generateFakeId(Account.SObjectType, 10);
Mock Parent Relationships
You can populate parent relationships in the same way you set field values. Use the lookup field (AccountId
or MyCustomParent__c
), so the library can automatically determine and validate your input for the sobject type of the parent:
Account mockAcc = (Account) TestMockFactory.createSObjectMock(
Account.SObjectType,
new Map<String, Object>{
'Name' => 'Test Account 2',
'OwnerId' => new Map<String, Object>{ 'Username' => '[email protected]' }
}
);
The lookup field (OwnerId
) and relationship field (Owner
) are automatically populated for you. The Id
of the parent is automatically generated. Alternatively, you can use an existing record to set the relationship. Unless you specify the Id manually, it is generated:
// set the lookup relationship using a mock account or queried account
Account mockedParentAcc = (Account) TestMockFactory.createSObjectMock(Account.SObjectType);
Contact mockContact = (Contact) TestMockFactory.createSobjectMock(
Contact.SObjectType,
new Map<String, Object>{
'AccountId' => mockedParentAcc,
'LastName' => 'Tester'
}
);
// set the lookup relationship using an in-memory sobject
Contact mockContact2 = (Contact) TestMockFactory.createSObjectMock(
Contact.SObjectType,
new Map<String, Object>{
'AccountId' => new Account(Name = 'Company Ltd.'),
'LastName' => 'Tester'
}
);
You can also set a plain Id as a value for the lookup field. If you do so, no parent object is created, and the relationship field (Account
) is also not initialized:
Contact mockContact3 = (Contact) TestMockFactory.createSObjectMock(
Contact.SObjectType,
new Map<String, Object>{
'AccountId' => '001000000000001AAA',
'LastName' => 'Tester'
}
);
Mock Child Relationships (Subselects)
In the same way, you would set a parent relationship or any field value, you can also populate child relationships. Simply use the relationship name (e.g. Contacts
, Assets
or CustomChildren__r
) in your input and specify the children as List<Map<String, Object>>
.
Account mockAcc = (Account) TestMockFactory.createSObjectMock(
Account.SObjectType,
new Map<String, Object>{
'Name' => 'Test Account 3',
'AccountNumber' => '1002',
'Contacts' => new List<Map<String, Object>>{
new Map<String, Object>{ 'LastName' => 'Kontakt 1' },
new Map<String, Object>{ 'LastName' => 'Kontakt 2' }
}
}
);
Alternatively, you can specify the children as List<SObject>
(e.g. List<Contact>
, List<Asset>
, etc). Similar to when you create a new sobject record, the Id is auto-generated, as long as you do not specify it:
Account mockAcc = (Account) TestMockFactory.createSObjectMock(
Account.SObjectType,
new Map<String, Object>{
'Name' => 'Test Account 3',
'AccountNumber' => '1002',
'Contacts' => new List<Contact>{
new Contact(LastName = 'Kontakt 1'),
new Contact(LastName = 'Kontakt 2'),
}
}
);
Override System- and Read-Only Fields
You can override any property that you would use for creating a new mock record. Properties encompass system fields, formula fields, compound fields, regular fields, and parent- and child relationships. The library uses a JSON internally, so the existing record is not modified. Instead, a new copy is created and returned.
Account a = new Account();
// override a field using the field token
a = (Account) TestMockFactory.overrideField(a, Schema.Account.CreatedDate, System.now());
// override a field using its developer name
a = (Account) TestMockFactory.overrideField(a, 'LastModifiedDate', System.now());
Additionally, you can override a child relationship:
a = (Account) TestMockFactory.overrideField(
a,
'Contacts',
new List<Contact>{
new Contact(LastName = 'Tester 1', Email = '[email protected]'),
new Contact(LastName = 'Tester 2', Email = '[email protected]'),
new Contact(LastName = 'Tester 3', Email = '[email protected]')
}
);
Set Record Types By Developer Names
Lastly, you can specify a record type for the new mock sobject. Simply set RecordTypeId
in the input using a valid developer name:
Order mockOrder = (Order) TestMockFactory.createSObjectMock(
Order.SObjectType,
new Map<String, Object>{
'OrderNumber' => '00000100',
'RecordTypeId' => 'My_Record_Type_Name'
}
);
Directly Access The Mock Factory Singleton
The heavy lifting is done by the TestMockFactory.SObjectMockFactory
class. You cannot construct it directly, instead, use getSObjectMockFactory(Schema.SObjectType)
. The individual sobject factories are designed as singletons, so subsequent calls with the same sobject token return the same factory instance.
TestMockFactory.SObjectMockFactory f = TestMockFactory.getSObjectMockFactory(Account.SObjectType);