Andy in the Cloud

From BBC Basic to Force.com and beyond…

Working with Apex Mocks Matchers and Unit Of Work

7 Comments

The Apex Mocks framework gained a new feature recently, namely Matchers. This new feature means that we can start verifying what records and their fields values are being passed to a mocked Unit Of Work more reliably and with a greater level of detail.

Since the Unit Of Work deals primarily with SObject types this does present some challenges to the default behaviour of Apex Mocks. Stephen Willcock‘s excellent blog points out the reasons behind this with some great examples. In addition prior to the matchers functionality, you could not verify your interest in a specific field value of a record, passed to registerDirty for example.

So first consider the following test code that does not use matchers.

	@IsTest
	private static void callingApplyDiscountShouldCalcDiscountAndRegisterDirty()
	{
		// Create mocks
		fflib_ApexMocks mocks = new fflib_ApexMocks();
		fflib_ISObjectUnitOfWork uowMock = new fflib_SObjectMocks.SObjectUnitOfWork(mocks);

		// Given
		Opportunity opp = new Opportunity(
			Id = fflib_IDGenerator.generate(Opportunity.SObjectType),
			Name = 'Test Opportunity',
			StageName = 'Open',
			Amount = 1000,
			CloseDate = System.today());
		Application.UnitOfWork.setMock(new List<Opportunity> { opp };);

		// When
		IOpportunities opps =
			Opportunities.newInstance(testOppsList);
		opps.applyDiscount(10, uowMock);

		// Then
		((fflib_ISObjectUnitOfWork)
			mocks.verify(uowMock, 1)).registerDirty(
				new Opportunity(
					Id = opp.Id,
					Name = 'Test Opportunity',
					StageName = 'Open',
					Amount = 900,
					CloseDate = System.today()));
	}

On the face of it, it looks like it should correctly verify that an updated Opportunity record with 10% removed from the Amount was passed to the Unit Of Work. But this fails with an assert claiming the method was not called. The main reason for this is its a new instance and this is not what the mock recorded. Changing it to verify with the test record instance works, but this only verifies the test record was passed, the Amount could be anything.

		// Then
		((fflib_ISObjectUnitOfWork)
			mocks.verify(uowMock, 1)).registerDirty(opp);

The solution is to use the new Matchers functionality for SObject’s. This time we can verify that a record was passed to the registerDirty method, that it was the one we expected by its Id and critically the correct Amount was set.

		// Then
		((fflib_ISObjectUnitOfWork)
			mocks.verify(uowMock, 1)).registerDirty(
				fflib_Match.sObjectWith(
					new Map<SObjectField, Object>{
						Opportunity.Id => opp.Id,
						Opportunity.Amount => 900} ));

There is also methods fflib_Match.sObjectWithName and fflib_Match.sObjectWithId as kind of short hands if you just want to check these specific fields. The Matcher framework is hugely powerful, with many more useful matchers. So i encourage you to take a deeper look David Frudd‘s excellent blog post here to learn more.

If you want to know more about how Apex Mocks integrates with the Apex Enterprise Patterns as shown in the example above, refer to this two part series here.

7 thoughts on “Working with Apex Mocks Matchers and Unit Of Work

  1. Andy, I’m looking to implement Enterprise Design Patterns for the ISV app my company purchased. Right now the app is a complete mess. Definitely no modular apex classes and even worse sObject schema. I’m going to follow your advice from how to switch to Enterprise Design Patterns and build my service layer first. Eventually I would like to setup Apex Mocks. I’ve read over your Application Factory pattern and other Unit of Work Documentation Do you have any advice or tools I could use for figuring my apps list of objects given in dependency order (least dependent first) for the fflib_SObjectUnitOfWork. Any help would be much appreciated.

    • Sorry nothing springs to mind. I would start by using Schema Builder under setup to understand the object model better. Then writing some test code or simple data setup scripts to drive a key process. This will help you understand what data is needed step by step. And give you some good resources out of it to use going forward for education and testing.

  2. OK, helpful as always but since registerDirty() can be passed a list of SObjects, how do you use fflib_Match.sObjectWith in a .verify ? The matcher: fflib_Match.sObjectWith matches a singleton arg to registerDirty and I’m puzzled as to the syntax to match against expected values of a list of updated sobjects sent to registerDirty by the underlying class under test.

    • Yes may need to add a new utilities methods for multiple. Take a look at the code for that method it should be easy to create a sObjectsWith method. If you struggle suggest you post an issue on the GitHub repo, the devs are quite active on that.

      • OK — I was hoping there was some clever way of using composition to get there. I’ll try what you suggested. Problem isn’t as straightforward as one might think as the order of Sobjects passed to registerDirty() or registerNew() may not be predictable to the testmethod as the underlying code may be registering sobjects to uow based on set or map order. Thus the matcher must go through the list 1×1 of actual registered sobjects comparing against the expected sobject until a match or list is exhausted (no match). Thus the matcher argument for sObjectsWith is not a list but a single sobject. Have I got this right?

      • It’s best if you raise on the GitHub and I can direct our current lead guy onto your question, drop me back the link here when you do and I will ping him internally as well

      • Issue raised here: https://github.com/financialforcedev/fflib-apex-mocks/issues/53. Thanks again for all that fflib has contributed to the community!

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