Andy in the Cloud

From BBC Basic to Force.com and beyond…

Mocking SOQL sub-select query results

8 Comments

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.

 

8 thoughts on “Mocking SOQL sub-select query results

  1. The sins we commit in the name of the Force 😉

  2. Pingback: Unit Testing, Apex Enterprise Patterns and ApexMocks – Part 2 | Andy in the Cloud

  3. How would you go about stitching something together in memory if you were instead mocking a query such as SELECT Id, Account.Name, Account.Industry, FirstName, LastName FROM Contact ?

  4. Hey Andy, Have we moved makerelationship into Apex Mocks? Also have we extended it to support multiple sub queries?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s