Newer Version Available

This content describes an older version of this product. View Latest

Mock SOQL Tests for External Objects

You can mock SOQL query responses for external objects in Apex testing by using SOQL stub methods and a new test class. Use basic and joined SOQL queries against external objects and return mock records in a testing context.

Create mock test classes by extending the new System.SoqlStubProvider class and overriding the handleSoqlQuery() class method. Create external object records using either Test.createStubQueryRow() or Test.createStubQueryRows(). Register the mock provider in the test using Test.createSoqlStub() and execute the test code.

Apex governor limits apply to the stubbed records.

Note

The SOQL query must be against an external object, either directly with a FROM clause or via a subquery. These features aren’t allowed within a stub implementation.

  • SOQL
  • SOSL
  • Callouts
  • Future methods
  • Queueable Jobs
  • Batch Jobs
  • DML
  • Platform events

This example shows a mock test class for the GithubIssueTest class with joined and basic queries.

1/**
2 *   Test class that utilizes the SoqlStubProvider classes.
3 *   Each test sets the appropriate SoqlStubProvider
4 *   and runs validation against the mocked query results.
5 **/
6
7@isTest
8public class GithubIssueTest {
9    @isTest
10    static void testGithubIssueQuery() {
11        QueryIssueUtil queryIssueUtil = new QueryIssueUtil();
12        SObjectType type = queryIssueUtil.getSObjectTypeForDynamicSoql('GithubIssues__x');
13        Test.createSoqlStub(type, new IssueStubProvider());
14        Test.startTest();
15        Assert.isTrue(Test.isSoqlStubDefined(type));
16        Assert.isTrue(queryIssueUtil.queryGithubIssuesAndCheckForId());
17        Assert.areEqual(Limits.getQueries(), 1);
18        Assert.areEqual(Limits.getQueryRows(), 1);
19        Assert.areEqual(Limits.getAggregateQueries(), 0);
20        Assert.isTrue(queryIssueUtil.queryGithubIssuesAndVerifyResultSize(1));
21        Assert.areEqual(Limits.getQueries(), 2);
22        Assert.areEqual(Limits.getQueryRows(), 2);
23        Assert.areEqual(Limits.getAggregateQueries(), 0);
24        Test.stopTest();
25    }
26
27    @isTest
28    static void testIssueToCommentJoinQuery() {
29        QueryIssueUtil queryIssueUtil = new QueryIssueUtil();
30        Test.createSoqlStub(GithubIssues__x.SObjectType, new IssueCommentJoinStubProvider());
31        Test.startTest();
32        Assert.isTrue(Test.isSoqlStubDefined(GithubIssues__x.SObjectType));
33        Assert.isTrue(queryIssueUtil.queryIssueToCommentJoinAndCheckForCommentId());
34        Assert.areEqual(Limits.getQueries(), 1);
35        Assert.areEqual(Limits.getQueryRows(), 3);
36        Assert.areEqual(Limits.getAggregateQueries(), 1);
37        Assert.isTrue(queryIssueUtil.queryIssueToCommentJoinAndVerifyResultSize(1, 2));
38        Assert.areEqual(Limits.getQueries(), 2);
39        Assert.areEqual(Limits.getQueryRows(), 6);
40        Assert.areEqual(Limits.getAggregateQueries(), 2);
41        Test.stopTest();
42    }
43}
1/**
2 *   SoqlStubProvider class that returns a mocked query result
3 *   for joined queries between the Github Issues object and
4 *   the associated Comments object.
5 **/
6
7public class IssueCommentJoinStubProvider extends SoqlStubProvider {
8    public override List<SObject> handleSoqlQuery(SObjectType sobjectType, String rawQuery, Map<String,Object> binds) {
9        if (sobjectType.equals(GithubIssues__x.SObjectType)) {
10            Assert.areEqual(binds.size(), 0);
11
12            List<GithubIssues__x> issues = new List<GithubIssues__x>();
13            List<Map<String,Object>> commentMaps = new List<Map<String,Object>>();
14
15            Map<String, Object> comment1 = new Map<String, Object> {
16                'Id' => 'x09xx000000brk9AAA'
17            };
18            Map<String, Object> comment2 = new Map<String, Object> {
19                'Id' => 'x09xx000001brk9AAA'
20            };
21
22            commentMaps.add(comment1);
23            commentMaps.add(comment2);
24
25            List<IssueComments__x> comments = (List<IssueComments__x>) Test.createStubQueryRows(IssueComments__x.SObjectType, commentMaps);
26
27            Map<String, Object> issueMap = new Map<String, Object> {
28                'Id' => 'x08xx000002HNZ6AAO',
29                'Title__c' => 'Sample Issue 1',
30                'IssueComments__r' => comments
31            };
32
33            GithubIssues__x obj = (GithubIssues__x) Test.createStubQueryRow(sobjectType, issueMap);
34
35            issues.add(obj);
36            return issues;
37        }
38        return null;
39    }
40}
1/**
2 *   SoqlStubProvider class that returns a mocked query result
3 *   for queries against the Github Issues object.
4 **/
5
6public class IssueStubProvider extends SoqlStubProvider {
7    public override List<SObject> handleSoqlQuery(SObjectType sobjectType, String rawQuery, Map<String,Object> binds) {
8        if (sobjectType.equals(GithubIssues__x.SObjectType)) {
9        Assert.areEqual(binds.size(), 1);
10        Assert.areEqual(binds.get('tmpVar1'), 'x08xx000002HNZ6AAO');
11
12            List<SObject> objs = new List<SObject>();
13            Map<String, Object> individualMap = new Map<String, Object> {
14                'Id' => 'x08xx000002HNZ6AAO'
15            };
16            GithubIssues__x obj = (GithubIssues__x) Test.createStubQueryRow(sobjectType, individualMap);
17            objs.add(obj);
18            return objs;
19        }
20        return null;
21    }
22}
1/**
2 *   Utility class that runs queries to be mocked 
3 *   in the Apex tests.  
4 **/
5
6public class QueryIssueUtil {
7    public boolean queryGithubIssuesAndCheckForId() {
8        // BINDS WITH USER_MODE DYNAMIC QUERY
9        Map<String, Object> binds = new Map<String, Object>{'tmpVar1' => 'x08xx000002HNZ6AAO'};
10        List<GithubIssues__x> issues = Database.queryWithBinds('SELECT Id FROM GithubIssues__x WHERE Id  = :tmpVar1', binds, AccessLevel.USER_MODE);
11
12        for (GithubIssues__x issue : issues ) {
13            if (issue.Id.equals('x08xx000002HNZ6AAO')) {
14                return true;
15            }
16        }
17        return false;
18    }
19
20    public boolean queryGithubIssuesAndVerifyResultSize(Integer size) {
21        // BINDS WITH SYSTEM_MODE STATIC QUERY
22        String issueId = 'x08xx000002HNZ6AAO';
23        List<GithubIssues__x> issues = [SELECT Id FROM GithubIssues__x WHERE Id  = :issueId];
24        if(issues.size() == size) {
25            return true;
26        }
27            return false;
28    }
29
30    public boolean queryIssueToCommentJoinAndCheckForCommentId() {
31        // DYNAMIC QUERY
32        List<GithubIssues__x> issues = Database.query('SELECT Id, Title__c, (SELECT Id FROM IssueComments__r) FROM GithubIssues__x WHERE Id = \'003000000000000\'');
33
34
35        for (GithubIssues__x issue : issues) {
36            List<IssueComments__x> comments = issue.IssueComments__r;
37            System.debug(comments);
38            if(!comments.get(0).Id.equals('x09xx000000brk9AAA') && !comments.get(1).Id.equals('x09xx000001brk9AAA'))return false;
39        }
40        return true;
41    }
42
43    public boolean queryIssueToCommentJoinAndVerifyResultSize(Integer parentSize, Integer childSize) {
44        // STATIC QUERY
45        List<GithubIssues__x> issues = [SELECT Id, Title__c, (SELECT Id FROM IssueComments__r) FROM GithubIssues__x WHERE Id = '003000000000000'];
46
47        if(issues.size() == parentSize && issues.get(0).IssueComments__r.size() == childSize) {
48            return true;
49        }
50        return false;
51    }
52
53    public SObjectType getSObjectTypeForDynamicSoql(String name) {
54        Schema.DescribeSObjectResult[] descResult = Schema.describeSobjects(new List<String>{name});
55        SObjectType type = descResult.get(0).getSobjectType();
56        return type;
57    }
58}