Apex Mocks and Enterprise Patterns (First in a Series)

Part one of a series. Posts include:

It took me a bit to get here but I can unequivocally state that Apex Mocks is well worth learning to improve your unit testing.

This blog post aims at the Apex developer who doesn’t have Java Mockito experience (which was me). Since most of the code at our org relies on the Apex Enterprise Patterns, the examples herein will exploit that. The most useful reference to me was chapter 11 – Unit Testing of Force.com Enterprise Architecture although there were some typos in the code examples in my printed copy.

See also Part 2 (Unit Testing email), and Part 3 (Selector mocking with formula fields)

The scenario
You have a CasesService, method reopen(set csIds)

Here’s the code: Note use of the Unit of Work Pattern

public virtual class CasesServiceImpl implements ICasesService{

    /**
    *	reopen	- reopen a set of Cases
    **/
    public virtual void reopen(set<ID> csIds) {
        try {
            fflib_ISobjectUnitOfWork uow = Application.UnitOfWork.newInstance();
        	reopen(uow,csIds);
        	uow.commitWork();
        }
        catch (Exception e) {
            throw new CasesService.CaseReopenException(e.getTypeName() + ' ' + e.getMessage() + ' ' + e.getStackTraceString());
        }
    }
    

    public virtual void reopen(fflib_ISobjectUnitOfWork uow, set<ID> csIds) {
        for (ID csId : csIds) {
            uow.registerDirty(new Case(Id = csId, Status = Cases.STATUS_NEW));
        }    
    }
    
}

I’m presuming if you are reading this, that you already know the Apex Enterprise Pattern and have created the ICasesService.cls, CasesService.cls, and updated the Application.cls.

Unit testing the service
In “classic” Apex development, you would test the CasesServiceImpl.reopen(set csIds) method by creating via DML a Case record (this might also require creating an Account and Contact record). Sort of like this:

@isTest private static void testCaseReopen() {
   Account[] mockAccts = new list<Account> {
     new Account(...),
     new Account(...)
     ...
   };
   insert mockAccts;
   // and so on for the Contacts (mockContacts) and Cases (mockCases)

   Test.startTest(); 
   CasesService.reopen(new set<ID> (new map<ID,Case>(mockCases).keySet());
   Test.stopTest();
   Case[] resultCases [select Id, Status from Case];
   for (Integer i = 0; i < resultCases.size(); i++)
      System.assertEquals('New',resultCases[i].Status,
                          '['+i+'] Status should be New');
}

So boring. And so much DML that makes your tests run longer – especially if you have hundreds or thousands of tests. You have to query the database to test your asserts. Think about testing Opportunities – Accounts, Contacts, Opportunities, Pricebooks, Product2s, PricebookEntries, and OpportunityLineItems may all have to be inserted into the database.

And here’s the beauty
Because you are using Apex Enterprise Patterns, you recognize that the reopen method only needs to be passed in Case Ids and then verified that the Cases were registered as dirty and that commitWork was called.

  • You don’t need real Cases to generate valid Case Ids
  • You don’t need to query the updated records to see if the Status was changed. Instead, you just need to know that registerDirty() was called with the expected status of ‘New’. As long as commitWork() was called, you can rely on the updates being done (written to the database) because commitWork() is already unit tested by the fflib library.


So, what does the unit test look like?

I’ll start off by saying that the syntax takes a bit of time getting used to. I’ll try to comment each step

@isTest private static void testReopen() {
  /** variable mocks represents the mocking framework object. 
     You can call it anything **/
  
  fflib_ApexMocks mocks = new fflib_ApexMocks();  // required and first
 
  // Given Case Ids to reopen 
  /** We use fflib to generate valid-for-the-sobjectType IDs **/
  
  ID[] mockCaseIds = new list<ID>();
  for (Integer i = 0; i < 2; i++) // 2 is arbitrary but goal is bulk testing 	
    mockCaseIds.add(fflib_IdGenerator.generate(Case.SobjectType));

  // Given mock UnitOfWork
  fflib_SobjectUnitOfWork mockUow = (fflib_SobjectUnitOfWork) 
                                       mocks.mock(fflib_SObjectUnitOfWork.class);
  Application.UnitOfWork.setMock(mockUow); // inject the mock for fflib factories

  /** Wrap your service call in try catch so you can easily debug any errors **/
  // When service invoked
  try {
   CasesService.reopen(new set<ID>(mockCaseIds)); 
  }
  catch(Exception e) {
   System.assert(false,'snb, service method sb success; ' + showException(e));
  }

  // Then verify Case status is New and objects committed 
  /** mocks.verify takes two arguments:
       1 - the object being mocked - in this case, the UnitOfWork
       2 - an object of type fflib_VerificationMode. 
           This object supports a fluent pattern with methods:
            times(n) - how many times the mock object's method M is called
            atLeast(n) - method called at least n times
            atMost(n) - method called at most n times
            atLeastOnce() - method called at least one time
            between(m,n) - method called between m and n times
            never() - method never called
            calls(n) - method called n times w/ InOrder verifier (see doc)
            description(text) - equivalent to the third argument for System.assertEquals()
  
       Once cast to the type of the mock object, then verification is that the 
       registerDirty method was called (twice) with an Sobject of type Case
  **/         
  ((fflib_SobjectUnitOfWork) 
    mocks.verify(mockUow,mocks
                  .times(2)
                  .description('both Cases should be marked as Closed'))
  )
   .registerDirty(fflib_Match.sObjectOfType(Case.SObjectType));


  /** In this verify, assert registerDirty was called with an SObject matching 
      1 - Case.Id = the first mocked CaseId
      2 - Case.Status is 'New'

     This is done with a matcher (type = fflib_Match) argument. 
     The framework looks to see if the method (registerDirty) 
     was called with an argument that "matches" the matcher.

     There are a large number of matchers, see class
     fflib_Match (most of the Mockito matchers are mirrored). 
     One of the Apex-specific ones is sObjectWith that takes a map of
     SObjectField => Object
  **/

  ((fflib_SobjectUnitOfWork) 
    mocks.verify(mockUow,mocks
                  .times(1)
                  .description('Case[0] sb reopened'))
  )
   .registerDirty(fflib_Match.sObjectWith(new map<SObjectField,Object> {
        Case.ID => mockCaseIds[0],    																			
        Case.Status => Cases.STATUS_NEW}));
		
  /** In this verify, assert the second Case ID also became status New **/
  ((fflib_SobjectUnitOfWork) 
    mocks.verify(mockUow,mocks
                  .times(1)
                  .description('Case[1] sb reopened'))
  )
   .registerDirty(fflib_Match.sObjectWith(new map<SObjectField,Object> {
        Case.ID => mockCaseIds[1],    																			
        Case.Status => Cases.STATUS_NEW}));

  /** Finally, assert records were committed to Database. 
      As commitWork has no args there is no Matcher **/

  ((fflib_SobjectUnitOfWork) 
    mocks.verify(mockUow,mocks
                   .times(1)
                   .description('both Cases should be committed'))
  )
   .commitWork();

}

Some meta remarks

  1. Using ApexMocks requires some wordy syntax that is not immediately apparent when you first see it
  2. It is especially useful for unit testing smallish methods that have few dependencies (which is how they should be written in the first place). Doing unit testing improves your confidence that methods will do what they are supposed to do when orchestrated into larger transactions.
  3. You can mock only non-static methods of objects.
  4. You need to invest time in learning how Matchers work. The class fflib_Match and fflib_ApexMocksTest are helpful. So are Mockito books and the Mockito web site
  5. As we’ll see in subsequent posts, you can mock selectors including formula fields and system audit fields without having to do any DML or SOQL. You can also mock the domain layer and service layer, and for that matter any non-static object method (except async)

The goal of this post was to explain the syntax. Try it out on a small class and you’ll feel you are up’ing your Unit test game, hence leaving a stronger legacy of test code to yourself, your team members, and your successors.

2 thoughts on “Apex Mocks and Enterprise Patterns (First in a Series)

  1. Igor

    Hi, could you please explain, why it doesn’t throw an exception on row 74, because registerDIrty() was called two times?

    Reply
    1. eric.kintzer@cropredy.com Post author

      So, look at line 48 – this confirms that registerDirty was called on a Case SobjectType twice. But line 74 is verifying that registerDirty was called only once for an Sobject whose Id matches mockCaseIds[0] and line 88 verifies that registerDirty was called for an sobject with Id = mockCaseIds[1].

      The key here is the matcher fflib_Match.sObjectWith that matches against all sobjects used in all registerDirty calls. By explicitly including the Case.Id => mockCaseIds[0] field in the matcher’s construction, the underlying matcher should eventually find one of the registerDirty sobject arguments (by matching on the ID field) and then will go on to match the other fields.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *