7

Canonical Question and Answer

When using ApexMocks, I'm getting unexpected results. What could be the possible reasons?

Here are some of the symptoms I observe:

  • Null pointer exception in my service class when I'm mocking a selector but no null pointer exception when querying DML'd SObjects
  • My verifies come back with something that looks like:

    FATAL_ERROR|fflib_ApexMocks.ApexMocksException: Expected : 1, Actual: 0 -- 
    Wanted but not invoked: fflib_SObjectUnitOfWork__sfdc_ApexStub.registerNew(SObject).
    
  • I observe in the debug log that a SOQL query is executed for a Selector that should be mocked and stubbed.

  • I'm mocking a service dependency to throw an exception but the code under test never receives an exception.
  • Code that used to work with ApexMocks no longer works when rerunning the testmethods.
  • My stubbing seems to do nothing and is as if not even recognized
0
12

Using ApexMocks is a powerful way to improve the unit testing of your code base and significantly improve the total turnaround time for all your tests as many tests can be done without having to do any DML

ApexMocks is a framework that works with well-designed applications that support dependency injection. However, you have to use it as designed otherwise your mocking results may not work as expected.

I'm going to make the assumption that you are using Apex Mocks in conjunction with the Force.com Enterprise Architecture pattern (aka fflib) as seen on Trailhead here and here

Some common reasons why your mocks aren't working

Note I assume you have at the beginning of your test method:

fflib_ApexMocks mocks = new fflib_ApexMocks();

I also assume you have set up your Application class per guidelines.

You forgot to inject the mock object (service, selector, domain, or unit of work)

  • The injection should be done after any mock object stubbing
  • For Unit Of Work: Application.UnitOfWork.setMock(mockUow);
  • For Service: Application.Service.setMock(ISomeService.class,mockService);
  • For Selector: Application.Selector.setMock(mockSelector);
  • For Domain: Application.Domain.setMock(mockDomain);

You are trying to stub a dependency but the code under test is passing arguments that are different than your mocked dependency expects

Example: Assume an object under test (MyClass) calls an Accounts Selector

AccountsSelector mockAccountsSelector = (AccountsSelector) mocks.mock(AccountsSelector.class);

mocks.startStubbing();
mocks.when(mockAccountsSelector.SObjectType()).thenReturn(Account.SObjectType);
mocks.when(mockAccountsSelector.selectById(someIdSet)).thenReturn(someMockAccounts);
mocks.stopStubbing();

Application.Selector.setMock(mockAccountsSelector);

new MyClass().doStuff();  // code under test

The mockSelector is expecting to be passed the contents of someIdSet (say id0 and id1) but if the MyClass object actually calls selectById with a different ID set, the stubbed selector will not return anything to MyClass's object and results could go haywire.

You are trying to stub a Selector dependency but forgot to stub the SObjectType() method

Using the same example as above

AccountsSelector mockAccountsSelector = (AccountsSelector) mocks.mock(AccountsSelector.class);

mocks.startStubbing();

// stub the SObject() method AND the specific selector method    
mocks.when(mockAccountsSelector.SObjectType())
   .thenReturn(Account.SObjectType);
mocks.when(mockAccountsSelector.selectById(someIdSet))
  .thenReturn(someMockAccounts);
mocks.stopStubbing();

Application.Selector.setMock(mockAccountsSelector);

Your selector class's super class (fflib_SObjectSelector) requires your selector class to implement method SObjectType() so your mock selector must stub it.

You are stubbing the wrong methods

Example:

You have a Contacts selector with methods selectById, selectByEmail, selectByPostalCode(). Your code under test calls selectByEmail but you didn't mock that method (or mocked selectByPostalCode or selectById by mistake).

This error comes up often when code is modified after unit testing is done. Imagine once you had some useful code that did ContactsSelector.newInstance().selectById(..) and you mocked that method in your unit test. Six months later, you go into the code and change the selector to be ContactsSelector.newInstance.selectByEmail(..) and then rerun your unit tests. They will fail because the wrong method is mocked in the unit test and hence no Contacts will be returned to the code under test.

You forgot to close your stubbing with `stopStubbing`

Example

AccountsSelector mockAccountsSelector = (AccountsSelector) mocks.mock(AccountsSelector.class);

mocks.startStubbing();

// stub the SObject() method AND the specific selector method    
mocks.when(mockAccountsSelector.SObjectType())
   .thenReturn(Account.SObjectType);
mocks.when(mockAccountsSelector.selectById(someIdSet))
  .thenReturn(someMockAccounts);
mocks.startStubbing();  // OOPS!  should be stopStubbing()

Application.Selector.setMock(mockAccountsSelector);

The failure to close the stubbing can lead to unpredictable results (and this is a common issue if you are copy-pasting the mocks.startStubbing() line!)

Note that mocks.doThrowWhen(..) must be enclosed within a startStubbing-stopStubbing pair

You are using Apex types as arguments to stubbed methods without a way to test equality

Example: You are mocking a service that receives as input a custom Apex Type

AccountsServiceImpl mockAccountsService = (AccountsServiceImpl) 
  mocks.mock(AccountsServiceImpl.class);

mocks.startStubbing();
mocks.when(mockAccountsService.execute(myApexTypes)
    .thenReturn(somethingUseful);
mocks.stopStubbing();

Application.Service.setMock(mockAccountsService);

new MyClass().doStuff();  // code under test

The issue here that by default, the stubbing framework has no way to test the equality of the collection myApexTypes against the actual collection of ApexType that you pass in the code under test (i.e. doStuff()). Note that Primitives/Sobjects can be tested for equality as can collections of primitives or sobjects.

To get around this, you either have to

  • Implement for class MyApexType the equals() and hashcode() method - see Apex Doc

  • Use a matcher that ignores the contents of the specific collection (defeating somewhat the rigor of your unit test for some use cases). Do this with a stubbing line such as:

      mocks.when(mockAccountService.execute((Set<MyApexType> fflib_Match.anyObject())
         .thenReturn(somethingUseful);
    

Your service redirector redirects to the wrong method

If you have code that looks like this:

AccountsService.doSomething(args);

and your AccountsService.cls has a redirector to the service factory methods like this:

public static void doSomething(args) {
   service().doSomethingElse(args);
}

public static void doSomethingElse(args) {
   service().doSomethingElse(args);
}

and your concrete service implementation class AccountServiceImpl looks like this:

public class AccountsServiceImpl implements IAccountsService {

  public void doSomething(Object args) {..}

  public void doSomethingElse(Object args) {..}
}

You have made a clumsy copy/paste error when you were adding the method in the AccountsService.cls

Instead of

public static void doSomething(args) {
   service().doSomethingElse(args);  // WRONG redirection
}

Use:

public static void doSomething(args) {
   service().doSomething(args);  // RIGHT redirection
}

You are trying to verify that Unit Of Work was passed an expected SObject but you are not verifying properly

This is better covered in this Andrew Fawcett blog post but the upshot is you should verify unit of work registerNew or registerUpdate actions using the Matcher sObjectWith (again, see the blog post for how to do this).

You are trying to use a Matcher but the runtime arg is null

Let's say you are stubbing a service method String result = doStuff(Integer i) as follows:

AccountsServiceImpl mockAccountsService = 
  (AccountsServiceImpl) mocks.mock(AccountsServiceImpl.class);
mocks.startStubbing();
mocks.when(mockAccountsService.doStuff(fflib_Match.anyInteger()))
   .thenReturn('Foo');
mocks.stopStubbing();
// Inject the mockService
Application.Service.setMock(mockAccountsService);

Now, if your runtime code under test calls doStuff(..) with a null argument, the mocks.when fflib_Match.anyInteger() will return false - that is, the criteria for the mocks.when(..) will not be satisfied and no stub return value Foo is supplied back to the code under test. Instead, null is returned and most likely, your testmethod will die on a null pointer exception.

Your code under test, after calling the mocked object, modified the argument used in the call to a mocked object

ApexMocks captures all method calls and their arguments. If the arguments are non-primitives, these are captured by reference. Thus, when you verify, you are most likely verifying the arguments as were passed to the mocked object but if the code under test subsequently modified those arguments, the verify will fail because you have also implicitly modified the recorded argument!.

That is, ApexMocks, when it records the method call + arguments doesn't make a copy of those arguments (if non-primitive).

For example, if your code under test does this:

 someMockableService.doStuff(someCollection);
 someCollection.clear();

and you verify that doStuff(..) was called with a collection that looked like someCollection (say, a list of specific Ids), the verify fails because ApexMocks thinks, by the time you verify, the recorded argument (originally non-empty) is now an empty list!

You can read a bigger example of this in ApexMocks GitHub Issue #78

You get a System.NullPointerException when doing a mocks.verify

Here, I'm assuming your verify looks like:

((MyType)mocks.verify(myMockedUowOrServiceOrSelectorOrDomain,mocks.times(1)
                       .description('some helpful message')))
                    .myMethod(expectedArg0, expectedArg1, ...);

Although there could be many reasons for this, check the following:

  • myMockedUowOrServiceOrSelectorOrDomain is null

UPDATE 2021-03-01

Additional diagnostic help is provided with the ApexMocks library as of Pull Request 112 which includes Feature/Better Verification Messages. This will show the actual args versus expected args rather than prior versions which only told you that the matcher failed, displaying only counts.

Additional References

4
  • 1
    Wow, you are my hero of the day, thanks!
    – Dirk
    Oct 8 '20 at 22:42
  • @Dirk - glad it helped; trust me, these were all learned the hard way with much head scratching. If your IDE supports code templates (like Illuminated Cloud), setting some up for mock selectors, domains, services, and units of work is worthwhile to avoid inadvertent errors
    – cropredy
    Oct 9 '20 at 2:55
  • @cropredy, I note the use of a method called when on the fflib_ApexMocks class. I also note that when is a reserved keyword in Apex. I'm surprised this hasn't caused issues and wonder if the Financial Force devs are aware?
    – Phil W
    Oct 26 '20 at 17:23
  • @PhilW - when as method name compiles just fine in V50.0; when as variable name does not compile - reserved word error; method sort() also compiles fine. I think that doc might incorrect.
    – cropredy
    Oct 26 '20 at 18:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.