4

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

3

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.
3
  • thank you so much for explaining. Is there not a way to eliminate DB interaction completely? By your code yes there is efficiency in terms of eliminating SOQL from getUserMap() method. But can it be made so we can mock User objects and inject them to avoid the List<User> users = TestDataFactory.seedTestUsers(200); Thank you again for your help and please forgive my lack of expertise in this.
    – kratos
    Dec 23 '19 at 7:03
  • 1
    @kratos List<User> users = TestDataFactory.seedTestUsers(200); is not really required. Check the updated test class.
    – javanoob
    Dec 23 '19 at 7:13
  • That worked great. Wanted to thank you again for a wonderful answer that helped clear the fog. Cheers !
    – kratos
    Dec 23 '19 at 21:34
1

Instead of creating multiple StubProvider classes it's better to use some framework in order to generate stubs on the fly. Check out https://github.com/apex-enterprise-patterns/fflib-apex-mocks. This framework uses JAVA mockito framework API.

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.