3

New to the SF dev world so I am struggling a bit with setting up some unit tests on external objects. I have found very little info on this, most dating back a few years.

I did find a post here on SO where I have adapted some of the code to get a test working. This seems to do the trick, however, it is structured for only testing a single external object, where I have several I need to test in my unit test.

Here is my code structure so far. The idea behind it is that you can just have your class pass data through it normally, but you can also override it to pass the mocked data that I choose.

// QueryPassthrough.cls
public virtual inherited sharing class QueryPassthrough {
    public static List<SObject> records(List<SObject> records) {
        return instance.passThrough(records);
    }

    static QueryPassthrough instance = new QueryPassthrough();
    @TestVisible static void setMock(QueryPassthrough mock) {instance = mock;}

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

-

// Test_EnterpriseUtilities.cls
@isTest
private class Test_EnterpriseUtilities {

    class Mock extends QueryPassthrough {
        final List<EntContact__x> externalContacts;

        Mock(List<EntContact__x> externalContacts) {
            this.externalContacts = externalContacts;
        }

        protected override List<SObject> passThrough(List<SObject> records) {
            return externalContacts;
        }
    }

    @isTest
    static testmethod void getExternalId() {
        Test.startTest();
            createData();
            String result = EnterpriseUtilities.GetExternalId('x0x3F00000HudxFQAR', 'EntContact__x');
        Test.stopTest();
        system.assertEquals('00T3F00000HudxFUAR', result, 'Expecting an ExternalId of 00T3F00000HudxFUAR from the mocked external contact');
    }

     static void createData() {

        // Create External Contact
        List<EntContact__x> mockExternalContacts = new List<EntContact__x>();
        mockExternalContacts.add(
            new EntContact__x(
                Name__c = 'Jim Test',
                ExternalId = '00T3F00000HudxFUAR'
            )
            );
        QueryPassthrough.setMock(new Mock(mockExternalContacts) );
     }

}

-

// EnterpriseUtilities.cls
@AuraEnabled(cacheable = true)
public static String GetExternalId(ID currentRecordId, String objName) {
    String query = 'SELECT ExternalId FROM ' + objName + ' WHERE id = :currentRecordId LIMIT 1';
    List<SObject> result = QueryPassthrough.records( Database.query(query) );
    String externalId = String.ValueOf( result[0].get('ExternalId') );

    return externalId != '' ? externalId : null;
}

While this works fine for the one external object I am mocking EntContact__x, I need to be able to support multiple objects. It was suggested to possibly use Map<SObjectType, List<SObject>> to handle multiples, but I am having trouble trying to figure out where to implement this.

My attempt, which failed, seems way off. Again, still trying to get the hang of this.

 final Map<SObjectType, List<SObject>> mockedData;
  Mock(Map<SObjectType, List<SObject>> mockedData) {
   this.mockedData = mockedData;
  }
  protected override Map<SObjectType, List<SObject>> passThrough(Map<SObjectType, List<SObject>> records) {
        return mockedData;
  }

// Fails with

@Override specified for non-overriding method: Map<Schema.SObjectType,List<SObject>> Test_EnterpriseUtilities.Mock.passThrough(Map<Schema.SObjectType,List<SObject>>)

Can anyone point out how I should implement the Map<SObjectType, List<SObject>>? End goal here is for my test class to be able to mock multiple external objects, where right now it is structured only to support the one.

Update:

Based on Answer

@isTest
private class Test_EnterpriseUtilities {
    class Mock extends QueryPassthrough {
        final Map<SObjectType, List<SObject> > cache;
        public Mock() {
            cache = new Map<SObjectType, List<SObject> >();
        }
        public Mock setDataStore(List<SObject> records) {
            cache.put(records.getSObjectType(), records);
            return this;
        }
        protected override List<SObject> passthrough(List<SObject> records) {
            return cache.get(records.getSObjectType() );
        }
    }
}

    /**
     * Create test data
     */
    static void createData() {
        // Test External Contact
        List<EntContact__x> mockExternalContacts = new List<EntContact__x>();
        mockExternalContacts.add(
            new EntContact__x(
            Name__c = 'Jim Test',
            ExternalId = '00T3G5430HudxFUAR'
            )
            );
        QueryPassthrough.setMock( new Mock(mockExternalContacts) );
    }

I may be missing something else here, but it is complaining about a constructor not being defined with these changes:

Constructor not defined: [Test_EnterpriseUtilities.Mock].<Constructor>(List<EntContact__x>) (115:35).

I left the QueryPassthrough.cls file unchanged and just made the update to the test.

1
  • The Salesforce Connect product team has a native mocking framework on their roadmap. Vote for the Idea here to try and get it prioritized. Nov 6, 2020 at 7:17

1 Answer 1

1
+50

Your cache is an entirely separate mechanism. The passthrough itself would just grab the appropriate collection.

protected override List<SObject> passthrough(List<SObject> records)
{
    return cache.get(records.getSObjectType());
}

Managing the cache itself isn't all that complicated.

@IsTest
public with sharing class QueryMock extends Query
{
    final Map<SObjectType, List<SObject>> cache;
    public QueryMock()
    {
        cache = new Map<SObjectType, List<SObject>>();
    }
    public QueryMock setDataStore(List<SObject> records)
    {
        cache.put(records.getSObjectType(), records);
        return this;
    }
}

You could add an overload signature which accepts the SObjectType too if you need it for better compatibility with your test data builder.

Notice that the constructor is empty (accepts no arguments). You need to set each data store via the fluent method provided.

QueryMock mock = new QueryMock()
    .setDataStore(new List<Case>())
    .setDataStore(new List<Lead>())
    .setDataStore(new List<My_External_Object__x());

Obviously, you would want to set up your data and populate those List with actual records.

5
  • Thanks - I tried to implement this and having an issue with it complaining about a constructor not being defined when trying to now run QueryPassthrough.setMock( new Mock(mockExternalContacts) ); Updated the question with the code changes.
    – SBB
    Feb 4, 2020 at 12:33
  • 1
    You don't add them in the constructor. Will update post.
    – Adrian Larson
    Feb 4, 2020 at 14:04
  • Ah okay, thanks!
    – SBB
    Feb 4, 2020 at 15:00
  • I think this is working, doing some testing now. I did still have to add Query.setMock(mock); at the end.
    – SBB
    Feb 4, 2020 at 19:20
  • Yes you still have to actually set the mock.
    – Adrian Larson
    Feb 4, 2020 at 19:22

You must log in to answer this question.

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