If your a fan of TDD you’ll hopefully have been following FinancialForce.com‘s latest open source contribution to the Salesforce community, known as ApexMocks. Providing a fantastic framework for writing true unit tests in Apex. Allowing you to implement mock implementations of classes used by the code your testing.
The ability to construct data structures returned by mock methods is critical. If its a method performing a SOQL query, there has been an elusive challenge in the area of queries containing sub-selects. Take a look at the following test which inserts and then queries records from the database.
@IsTest private static void testWithDb() { // Create records Account acct = new Account( Name = 'Master #1'); insert acct; List<Contact> contacts = new List<Contact> { new Contact ( FirstName = 'Child', LastName = '#1', AccountId = acct.Id), new Contact ( FirstName = 'Child', LastName = '#2', AccountId = acct.Id) }; insert contacts; // Query records List<Account> accounts = [select Id, Name, (select Id, FirstName, LastName, AccountId from Contacts) from Account]; // Assert result set assertRecords(acct.Id, contacts[0].Id, contacts[1].Id, accounts); } private static void assertRecords(Id parentId, Id childId1, Id childId2, List<Account> masters) { System.assertEquals(Account.SObjectType, masters.getSObjectType()); System.assertEquals(Account.SObjectType, masters[0].getSObjectType()); System.assertEquals(1, masters.size()); System.assertEquals(parentId, masters[0].Id); System.assertEquals('Master #1', masters[0].Name); System.assertEquals(2, masters[0].Contacts.size()); System.assertEquals(childId1, masters[0].Contacts[0].Id); System.assertEquals(parentId, masters[0].Contacts[0].AccountId); System.assertEquals('Child', masters[0].Contacts[0].FirstName); System.assertEquals('#1', masters[0].Contacts[0].LastName); System.assertEquals(childId2, masters[0].Contacts[1].Id); System.assertEquals(parentId, masters[0].Contacts[1].AccountId); System.assertEquals('Child', masters[0].Contacts[1].FirstName); System.assertEquals('#2', masters[0].Contacts[1].LastName); }
Now you may think you can mock the results of this query by simply constructing the required records in memory, but you’d be wrong! The following code fails to compile with a ‘Field is not writeable: Contacts‘ error on line 16.
// Create records in memory Account acct = new Account( Id = Mock.Id.generate(Account.SObjectType), Name = 'Master #1'); List<Contact> contacts = new List<Contact> { new Contact ( Id = Mock.Id.generate(Contact.SObjectType), FirstName = 'Child', LastName = '#1', AccountId = acct.Id), new Contact ( Id = Mock.Id.generate(Contact.SObjectType), FirstName = 'Child', LastName = '#2', AccountId = acct.Id) }; acct.Contacts = contacts;
While Salesforce have gradually opened up write access to previously read only fields, the most famous of which being Id, they have yet to enable the ability to set the value of a child relationship field. Paul Hardaker contacted me recently to ask if this problem had been resolved, as he had the very need described above. Using his ApexMock’s framework he wanted to mock the return value of a Selector class method that makes a SOQL query with a sub-select.
Driven by an early workaround (I believe Chris Peterson found) to the now historic inability to write to the Id field. I started to think about using the same approach to stich together parent and child records using the JSON serialiser and derserializer. Brace yourself though, because its not ideal, but it does work! And i’ve managed to wrap it in a helper method that can easily be adapted or swept out if a better solution presents itself.
@IsTest private static void testWithoutDb() { // Create records in memory Account acct = new Account( Id = Mock.Id.generate(Account.SObjectType), Name = 'Master #1'); List<Contact> contacts = new List<Contact> { new Contact ( Id = Mock.Id.generate(Contact.SObjectType), FirstName = 'Child', LastName = '#1', AccountId = acct.Id), new Contact ( Id = Mock.Id.generate(Contact.SObjectType), FirstName = 'Child', LastName = '#2', AccountId = acct.Id) }; // Mock query records List<Account> accounts = (List<Account>) Mock.makeRelationship( List<Account>.class, new List<Account> { acct }, Contact.AccountId, new List<List<Contact>> { contacts }); // Assert result set assertRecords(acct.Id, contacts[0].Id, contacts[1].Id, accounts); }
NOTE: Credit should also go to Paul Hardaker for the Mock.Id.generate method implementation.
The Mock class is provided with this blog as a Gist but i suspect will find its way into the ApexMocks at some point. The secret of this method is that it leverages the fact that we can in a supported way expect the platform to deserialise into memory the following JSON representation of the very database query result we want to mock.
[ { "attributes": { "type": "Account", "url": "/services/data/v32.0/sobjects/Account/001G000001ipFLBIA2" }, "Id": "001G000001ipFLBIA2", "Name": "Master #1", "Contacts": { "totalSize": 2, "done": true, "records": [ { "attributes": { "type": "Contact", "url": "/services/data/v32.0/sobjects/Contact/003G0000027O1UYIA0" }, "Id": "003G0000027O1UYIA0", "FirstName": "Child", "LastName": "#1", "AccountId": "001G000001ipFLBIA2" }, { "attributes": { "type": "Contact", "url": "/services/data/v32.0/sobjects/Contact/003G0000027O1UZIA0" }, "Id": "003G0000027O1UZIA0", "FirstName": "Child", "LastName": "#2", "AccountId": "001G000001ipFLBIA2" } ] } } ]
The Mock.makeRelationship method turns the parent and child lists into JSON and goes through some rather funky code i’m quite proud off, to splice the two together, before serialising it back into an SObject list and vola! It currently only supports a single sub-select, but can easily be extended to support more. Regardless if you use ApexMocks or not (though you really should try it), i hope this helps you write a few more unit tests than you’ve previous been able.