[SalesForce] How to use Stub API in the following scenario

I am trying to understand Stub API a bit:

My Class:

public class DistributedMSelector {

    public static  Map<string, user> getUserMap(Set<String> employeeIdSet){
        Map<String, User> employeeData = new Map<String, User> ();
        for(user u: [SELECT id, employeeNumber, firstName, lastName, email FROM User WHERE employeeNumber in:employeeIdSet ]){
            employeeData.put(u.employeeNumber, u);
        }
        return employeeData;
    }
}

My existing Test:

private class DistributedMSelector_Test {

    @IsTest
    static void testGetUserMap() {
        List<User> users = TestDataFactory.seedTestUsers(200);
        Set<String> IDs = new Set<String>();
        for (User u : users) {
            IDs.add(u.employeeNumber);
        }
        Test.startTest();
        Map<String, User> userMap = DistributedMSelector.getUserMap(IDs);
        Test.stopTest();
        System.assertEquals(200, userMap.keySet().size());
    }

Some Stub API setup code that I got from salesforce:

@isTest
public class MockProvider implements System.StubProvider {

    public Object handleMethodCall(Object stubbedObject, String stubbedMethodName, 
        Type returnType, List<Type> listOfParamTypes, List<String> listOfParamNames, 
        List<Object> listOfArgs) {

        // The following debug statements show an example of logging 
        // the invocation of a mocked method.

        // You can use the method name and return type to determine which method was called.
        System.debug('Name of stubbed method: ' + stubbedMethodName);
        System.debug('Return type of stubbed method: ' + returnType.getName());

        // You can also use the parameter names and types to determine which method 
        // was called.
        for (integer i =0; i < listOfParamNames.size(); i++) {
            System.debug('parameter name: ' + listOfParamNames.get(i));
            System.debug('  parameter type: ' + listOfParamTypes.get(i).getName());
        }

        // This shows the actual parameter values passed into the stubbed method at runtime.
        System.debug('number of parameters passed into the mocked call: ' + 
            listOfArgs.size());
        System.debug('parameter(s) sent into the mocked call: ' + listOfArgs);

        // This is a very simple mock provider that returns a hard-coded value 
        // based on the return type of the invoked.
        if (returnType.getName() == 'String')
            return '8/8/2016';
        else 
            return null;
    }
}

And the MockUtil:

public class MockUtil {
    private MockUtil(){}

    public static MockProvider getInstance() {
        return new MockProvider();
    }

     public static Object createMock(Type typeToMock) {
        // Invoke the stub API and pass it our mock provider to create a 
        // mock class of typeToMock.
        return Test.createStub(typeToMock, MockUtil.getInstance());
    }
}

Can someone please kindly show me how I would refactor my test method to use Stub API so I can test getUserMap without a ton of SOQL ? While I get the basic example shown at https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_stub_api.htm, I need to understand how I can stub out user ids etc.

Thank You in advance

Best Answer

To use stub API, we cannot have inline SOQL, so change DistributedMSelector as below:

public class DistributedMSelector {

    public static UserSelector userSelectorObj;

    public static  Map<string, user> getUserMap(Set<String> employeeIdSet){
        Map<String, User> employeeData = new Map<String, User> ();
        for(user u: userSelectorObj.queryUsers(employeeIdSet)){
            employeeData.put(u.employeeNumber, u);
        }
        return employeeData;
    }
}

Create a new class named UserSelector as below:

public class UserSelector {

    public List<User> queryUsers(Set<String> employeeIdSet){
        return [SELECT id, employeeNumber, firstName, lastName, email FROM User WHERE employeeNumber in:employeeIdSet ];
    }
}

Update MockProvider class as below:

@isTest
public class MockProvider implements System.StubProvider {

    public static List<User> userList;

    public Object handleMethodCall(Object stubbedObject, String stubbedMethodName, 
        Type returnType, List<Type> listOfParamTypes, List<String> listOfParamNames, 
        List<Object> listOfArgs) {

        if (stubbedMethodName == 'queryUsers'){
            return userList;
        }

    }
}

And finally change your test class as below:

private class DistributedMSelector_Test {

    @IsTest
    static void testGetUserMap() {
        String userListJSON = '[{"attributes":{"type":"User"},"Id":"005M0000007s8M2IAI", "EmployeeNumber": "abc"}]'; 
        MockProvider.userList = (List<User>)JSON.deserialize(quotesListJSON, List<User>.class);
        DistributedMSelector.userSelectorObj = (UserSelector)MockUtil.createMock(UserSelector.class);
        Map<String, User> userMap = DistributedMSelector.getUserMap(new Set<String>{'abc'});
        System.assertEquals(1, userMap.keySet().size());
    }
}

Here is how it works:

  1. To use stub API, don't do inline SOQL but instead use a selector and inject that selector to the actual class.
  2. Update MockProvider to return what you want when it is invoked in the context of test class. Check the if condition inside MockProvider.handleMethodCall. You can enhance it further.
  3. Test class should create mock selector object and inject this object to the actual class. Also, set what you want the MockProvider to return when it is invoked.
Related Topic