1

I have a class I need to write an apex test class for that uses the UserServicePresence object for Omni-Channel as a major filter for a SOQL query in the class. The majority of the logic in the class is bypassed if no users are found online in this query. I have been unable to create a temporary or mock UserServicePresence record in my apex test class. I have tried performing the DMl explicitly on the UserServicePresence SObject as well as the generic SObject.

The specific block of code I am trying to test is below. If the UserServicePresence map is empty, the rest of the method needs to be bypassed and then more logic in the class is then subsequently bypassed:

 private static map<String,UserServicePresence[]> buildMTPresenceMap(map<Id, Mission_Team__c[]> mmSMMissionTeams) {
        // Set collection of mission team names to match against public group names
        system.debug('MISSION TEAM MAP == '+mmSMMissionTeams);
        set<String> sUserIds = new set<String>();
        map<Id, set<Id>> mtAgents = new map<Id,set<Id>>();
        set<Id> mtIds = new set<Id>();
        map<Id, UserServicePresence> mServicePresence = new map<Id, UserServicePresence>();
        map<String, UserServicePresence[]> mMissionTeamSP = new map<String, UserServicePresence[]>();
        //system.debug('MISSION TEAM NAME SET === ' + sMTNames);
        for(Mission_Team__c[] mtList : mmSMMissionTeams.values()) {
            for(Mission_Team__c mt : mtList) {
                mtIds.add(mt.Id);
            }
        }
        // Loop through mission team members and build team member reference map
        for(Mission_Team_Member__c mtm : [SELECT Agent__c, Mission_Team__c
                                          FROM Mission_Team_Member__c
                                          WHERE Mission_Team__c IN :mtIds]) {
            if(mtAgents.containsKey(mtm.Mission_Team__c)) {
                mtAgents.get(mtm.Mission_Team__c).add(mtm.Agent__c);    
            } else {
                mtAgents.put(mtm.Mission_Team__c, new set<Id>{mtm.Agent__c});
            }
            sUserIds.add(mtm.Agent__c);             
        }
        system.debug('USER ID SET == ' + sUserIds);
        // Query user service presence
        for(UserServicePresence usp : [SELECT UserId
                                       FROM UserServicePresence
                                       WHERE UserId IN :sUserIds
                                       AND isAway = false
                                       AND StatusEndDate = null
                                       AND ServicePresenceStatus.DeveloperName != :LIVE_AGENT_PRESENCE_STATUS]) {
            mServicePresence.put(usp.UserId, usp);
            system.debug('SERVICE PRESENCE MAP UPDATED == ' + mServicePresence);    
        }
        // End processing if there are no active user service presence records
        if(mServicePresence.isEmpty()) {
            return mMissionTeamSP;
        }
        // Create user service presence map with mission teams
        for(Id missionTeamId : mtAgents.keySet()) {
            for(Id userId : mtAgents.get(missionTeamId)) {
                // Check if the user service presence map contains the user id from the membership groups map before using it as a reference
                if(!mServicePresence.containsKey(userId)) {
                    continue;
                }
                if(mMissionTeamSP.containsKey(missionTeamId)) {
                    mMissionTeamSP.get(missionTeamId).add(mServicePresence.get(userId));    
                } else {
                    mMissionTeamSP.put(missionTeamId, new UserServicePresence[]{mServicePresence.get(userId)});
                }
            }
        }
        system.debug('MISSION TEAM SERVICE PRESENCE MAP === ' + mMissionTeamSP);
        return mMissionTeamSP;
    }

Using the generic SObject method, I wrote the following code:

// Create test service presence status
        Schema.SObjectType spType = Schema.getGlobalDescribe().get('ServicePresenceStatus');
        SObject sps = spType.newSObject();
        sps.put('Language', 'en_US');
        sps.put('MasterLabel', 'Available Cases Label');
        sps.put('DeveloperName', 'Available_Cases');
        Database.SaveResult sr = Database.insert(sps, true);

        // Create test user service presence
        Schema.SObjectType uspType = Schema.getGlobalDescribe().get('UserServicePresence');
        SObject usp = uspType.newSObject();
        usp.put('IsAway', 'false');
        usp.put('Name', 'Test User Service Presence');
        usp.put('ServicePresenceStatusId', sr.getId());
        usp.put('StatusStartDate', system.now());
        usp.put('UserId', userId);
        Database.insert(usp, true);

In both cases, I receive the following error message:

System.TypeException: DML not allowed on ServicePresenceStatus

Has anyone discovered a way to insert or mock a UserServicePresence record in an apex test class that would be recognized by the regular apex class?

  • Why do you need to insert it? You ought to be able to write service methods which operate on records passed in, which you wouldn't have to insert in your test. – Adrian Larson Aug 8 '18 at 19:01
  • @AdrianLarson I'm not sure what you mean. – Daniel Fuller Aug 8 '18 at 19:07
  • 1
    Please edit your post to include the code you are trying to test. – Adrian Larson Aug 8 '18 at 19:07
  • @AdrianLarson, I added the method I am need to run in order to test the functionality. Let me know if there is anything else I can provide. Thanks for the help. – Daniel Fuller Aug 8 '18 at 19:12
3

I recommend you use query mocking here. It's a flexible concept and you can build it many different ways. It's a very useful pattern for data types you cannot insert in a test context.

The simplest build is something like the following:

public virtual class Query
{
    static Query instance = new Query();
    @TestVisible void setMock(Query mock) { instance = mock; }

    public static List<SObject> records(List<SObject> input)
    {
        return instance.passThrough(input);
    }

    protected virtual List<SObject> passThrough(List<SObject> input) { return input; }
}

Doesn't look like much, but it has power. First step would be to wrap this function call around the query you wish to mock:

List<UserServicePresence> records = Query.records([
    SELECT ... FROM UserServicePresence WHERE ...
]);

Then in your test, you can simply overwrite the value the system will give you back.

class UserServicePresenceQuery extends Query
{
    protected override List<SObject> passThrough()
    {
        return new List<UserServicePresence>
        {
            new UserServicePresence(
                UserId=UserInfo.getUserId()
                // set other fields as needed
                // using comma separated key=value pairs
            )
        };
    }
}

If you need to mock the query results for multiple queries in a single call, you can build out a mechanism to map a separate data store for each using Map<SObjectType, List<SObject>, or something along those lines.

  • Very interesting approach. I haven't used a pattern like this before, but it looks really helpful. Is this setMock() method used again somewhere that you have there in the Query class? – Daniel Fuller Aug 14 '18 at 17:27
  • @DanielFuller Ah yes I suppose I left that piece out of my answer. You would call Query.setMock(new UserServicePresenceQuery()). If you make the mock a top level class make sure it's @IsTest public. But there's no problem making such mocks local. – Adrian Larson Aug 14 '18 at 17:54
  • Thanks. I am actually still getting a similar compile error when I try to create a new instance of the UserServicePresence() object like in the last code block you mentioned there. For some of the fields I am trying to set, I still get the compile error that they are not writeable. Examples are "IsAway" and "StatusEndDate" – Daniel Fuller Aug 14 '18 at 19:36

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.