0

I'm trying to unit test a very simple Account service but for some reason it appears the setMock() is not working. I'm using the following unit test example as a guide and I'm pretty positive I understand the concept pretty thoroughly but when debugging it appears my Selector is actually being invoked rather than mocked. Here's my code w/ notes of renaming that we've implemented

AccountsASP.cls (ASP = Account Service Provider)

global with sharing class AccountsASP {

    public static Account getAccount(Id id){return service().getAccount(id);}

    private static IAccountsService service() {
        return (IAccountsService) Application.Service.newInstance(IAccountsService.class);
    }

}

AccountsService.cls (this is the implementation class)

public class AccountsService implements IAccountsService {

    public Account getAccount(Id id){
        List<Account> accounts = AccountsSelector.newInstance().selectById(new Set<Id>{id});
        if(accounts.size() > 0) {
            return accounts[0];
        } else {
            return NULL;
        }
    }

}

AccountsServiceTest.cls

@IsTest
private class AccountsServiceTest {

    @IsTest
    private static void getAccount() {
        /* Create mocks */
        fflib_ApexMocks mocks = new fflib_ApexMocks();
        IAccountsSelector selectorMock = new Mocks.AccountsSelector(mocks);

        /* Setup mock stubs */
        mocks.startStubbing();

        /* Generate a generic account id */
        Id aid = fflib_IDGenerator.generate(Account.SObjectType);
        /* Create our custom object but don't insert it into the database */
        List<Account> accounts = new List<Account>{
            new Account(
                Id = aid,
                Name = 'Test Account'
            )
        };
        Set<Id> accountSet = new Map<Id, Account>(accounts).keySet();

        mocks.when(selectorMock.selectById(accountSet)).thenReturn(accounts);
        mocks.stopStubbing();

        Application.Selector.setMock(selectorMock);

        /* Unit Test */
        Account result = AccountsASP.getAccount(aid);

        /* Behavior Test */
        ((IAccountsSelector) mocks.verify(selectorMock)).selectById(accountSet);
        // ^^ Assert fails
    }

}

When I debug accounts in the unit test I get the expect account list back:

(Account:{Id=001000000000001AAA, Name=Test Account})

But if I debug the actual service (AccountsService.getAccount()) I see that the Application.Selector.selectById() method was invoked even though it should call the selectorMock since I set it with the following:

Application.Selector.setMock(selectorMock);

Is there something I'm missing here?

Update: Added Mocks.cls

Mocks.cls

@isTest
public class Mocks
{
    public class AccountsSelector extends SObjectMocks.SObjectSelector implements IAccountsSelector
    {
        private fflib_ApexMocks mocks;

        public AccountsSelector(fflib_ApexMocks mocks)
        {
            super(mocks);
            this.mocks = mocks;
        }

        public List<Account> selectById(Set<ID> idSet)
        {
            return (List<Account>) mocks.mockNonVoidMethod(this, 'selectById', new List<Type> {System.Type.forName('Set<ID>')}, new List<Object> {idSet});
        }
    }
}
6
  • 1
    I think you're missing a mocks.when for your selector class, something like mocks.when(selectorMock.sObjectType()).thenReturn(Account.SObjectType); Commented Feb 15, 2017 at 23:06
  • In the AccountsServiceTest.cls I have the following mocks.when(selectorMock.selectById(accountSet)).thenReturn(accounts); Are you referring to elsewhere I need to include another when()? Commented Feb 15, 2017 at 23:53
  • 1
    add what I have in my comment above right above the current when. If you're following the typical pattern in that library you have a method in your selector to pass the object type. If this is the case, you need to mock that method first otherwise the mocking library doesn't find a match. Commented Feb 15, 2017 at 23:55
  • 1
    Application.Selector.selectById() is just a helper method that routes through the selector factory which eventually routes into your mock. So this is as expected. Commented Feb 16, 2017 at 1:18
  • 1
    @techbusinessman It appears you had the right idea, I'm still not sure I fully understand why I need to add that line, and is that only for mocking selectors? If you wouldn't mind submitting your answer with an explanation I would accept it. Thanks so much for your help! Commented Feb 16, 2017 at 18:04

1 Answer 1

2

In order to have your unit test mock the selector correctly you need to add the following line:

mocks.when(selectorMock.sObjectType()).thenReturn(Account.SO‌​bjectType); 

So your final code would like like the following:

@IsTest
private class AccountsServiceTest {

@IsTest
private static void getAccount() {
    /* Create mocks */
    fflib_ApexMocks mocks = new fflib_ApexMocks();
    IAccountsSelector selectorMock = new Mocks.AccountsSelector(mocks);

    /* Setup mock stubs */
    mocks.startStubbing();

    /* Generate a generic account id */
    Id aid = fflib_IDGenerator.generate(Account.SObjectType);
    /* Create our custom object but don't insert it into the database */
    List<Account> accounts = new List<Account>{
        new Account(
            Id = aid,
            Name = 'Test Account'
        )
    };
    Set<Id> accountSet = new Map<Id, Account>(accounts).keySet();

    mocks.when(selectorMock.sObjectType()).thenReturn(Account.SO‌​bjectType); 
    mocks.when(selectorMock.selectById(accountSet)).thenReturn(accounts);
    mocks.stopStubbing();

    Application.Selector.setMock(selectorMock);

    /* Unit Test */
    Account result = AccountsASP.getAccount(aid);

    /* Behavior Test */
    ((IAccountsSelector) mocks.verify(selectorMock)).selectById(accountSet);
    // ^^ Assert fails
}
}

As Andrew Fawcett points out in his comment "Application.Selector.selectById() is just a helper method that routes through the selector factory which eventually routes into your mock. So this is as expected."

Ultimately, you need to ensure that you not only mock what you are returning, but you also need to mock methods used by the library when using library methods, such as selectById.

3
  • So with every mock I need to create a mock for any subsequent call it makes to itself? So I have to be aware of everything that the library is doing in order to mock it? And this is true of mocking Selector, Domain, or UOW? Commented Feb 17, 2017 at 18:08
  • You have to be aware of the methods you are implementing from the interface. So for example, the selectById method you use is actually calling selectSObjectsById() which is an internal library call in fflib_SObjectSelector. From what I've found, the Selector layer is really the only place this is found. Commented Feb 17, 2017 at 19:57
  • How does this differ now with the Stub API. Do I simply change the mock create line to: AccountsSelector mockSelector = (AccountsSelector)mocks.mock(AccountsSelector.class);? Commented Dec 19, 2017 at 4:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .