top of page
Search
  • Shreyas Dhond

Only way to work with External Objects (Salesforce Connect) in Apex - Part 2 (Mocking External and Indirect Lookup Relationships)

Background

In my last blog we discussed a design pattern we can use to mock external object records for a single independent external object. But this model needs to modified/extended for supporting mocking data for indirect and external lookup relationships. 


Implementation

First modification is to make the ExternalObjectModelBase truly abstract to give complete control to the implementation of the external object models on how they want to create their test records. You could keep the base implementation a virtual class with very basic implementation of addTestRecord and addTestRecords for independent external objects. But it is probably better abstraction if it is kept as a contract instead.


Secondly making the mocking functionality @TestVisible prevents inadvertent usage other than for mocking unit testing data.

public with sharing abstract class ExternalObjectModelBase {
    @TestVisible protected Map<Object, SObject> mockedRecordMap =
        new Map<Object, SObject>();

    @TestVisible abstract public void addTestRecord(SObject record);
    @TestVisible abstract public void addTestRecords(List<SObject> records);
}

Implementation of ExternalOrderModel with implementations for addTestRecord and addTestRecords. Allows more control on how test records need to be created and indexed to facilitate other object functionality.

public with sharing class ExternalOrderModel extends ExternalObjectModelBase{

    private static ExternalOrderModel uniqueInstance;

    //Private Constructor for Singleton
    private ExternalOrderModel() {

    }

    //Singleton getter
    public static ExternalOrderModel getInstance() {
        if(uniqueInstance == null)
            uniqueInstance = new ExternalOrderModel();

        return uniqueInstance;
    }

    //Implement abstract methods
    public override void addTestRecord(SObject record) {
        if(record instanceof Orders__x) {
            Orders__x order = (Orders__x) record;
            mockedRecordMap.put((Integer) order.orderID__c, order);
        } else {
            throw new TestException('Not a valid order record.');
        }
    }

    public override void addTestRecords(List<SObject> records) {
        if(records instanceof List<Orders__x>) {
            List<Orders__x> orders = (List<Orders__x>) records;
            for(Orders__x order : orders) {
                mockedRecordMap.put((Integer) order.orderID__c, order);
            }
        } else {
            throw new TestException('Not a valid order record.');
        }
    }


    public Orders__x findByOrderId(String orderId) {
        Integer orderIdNumber = Integer.valueOf(orderId);
        List<Orders__x> orderList = [SELECT customerID__c, orderDate__c,
                                           orderID__c, shippedDate__c
                                    From Orders__x
                                    Where orderId__c =: orderIdNumber];

        return (Test.isRunningTest()) ? (Orders__x) mockedRecordMap.get(orderIdNumber) :
                (orderList.size() > 0) ? orderList[0] : null;
    }

    public List<Orders__x> findOrdersByCustomerId(String customerId) {
        List<Orders__x> orderList = [SELECT customerID__c, orderDate__c,
                                            orderID__c, shippedDate__c
                                     From Orders__x
                                     Where customerID__c =: customerId];

        if(Test.isRunningTest()) {
            for(Orders__x order : (List<Orders__x>) mockedRecordMap.values()) {
                if(order.customerID__c == customerId) {
                    orderList.add(order);
                }
            }
        }

        return orderList;
    }
}

Similar implementation of ExternalOrderDetailModel with custom implementation for addTestRecord and addTestRecords.

public with sharing class ExternalOrderDetailModel extends ExternalObjectModelBase {
    private static ExternalOrderDetailModel uniqueInstance;

    //Private Constructor for Singleton
    private ExternalOrderDetailModel() {

    }

    //Singleton getter
    public static ExternalOrderDetailModel getInstance() {
        if(uniqueInstance == null)
            uniqueInstance = new ExternalOrderDetailModel();

        return uniqueInstance;
    }

    //Implement abstract methods
    @TestVisible public override void addTestRecord(SObject record) {
        if(record instanceof OrderDetails__x) {
            OrderDetails__x orderDetail = (OrderDetails__x) record;
            addOrderDetail(orderDetail);
        } else {
            throw new TestException('Not a valid order record.');
        }
    }

    @TestVisible public override void addTestRecords(List<SObject> records) {
        if(records instanceof List<OrderDetails__x>) {
            List<OrderDetails__x> orderDetails = (List<OrderDetails__x>) records;
            for(OrderDetails__x orderDetail : orderDetails) {
                addOrderDetail(orderDetail);
            }
        } else {
            throw new TestException('Not a valid order record.');
        }
    }

    public List<OrderDetails__x> getOrderDetails(String orderId) {
        List<OrderDetails__x> orderDetails = [SELECT orderID__c, orderLine__c, product__c,
                                                    quantity__c, unitPrice__c
                                             From OrderDetails__x
                                             Where orderID__c =: orderId];

        if(Test.isRunningTest()) {
            for(OrderDetails__x orderDetail : (List<OrderDetails__x>) mockedRecordMap.values()) {
                if(orderDetail.orderID__c == orderId) {
                    orderDetails.add(orderDetail);
                }
            }
        }

        return orderDetails;
    }

    //Utility Functions
    @TestVisible private void addOrderDetail(OrderDetails__x orderDetail) {
        //
        if(String.isBlank(orderDetail.orderID__c))
            throw new TestException('Order ID not specified on order detail.');
        if(orderDetail.orderLine__c == null)
            throw new TestException('Order Line not specified on order detail.');

        //Parent Order Detail
        Orders__x order = ExternalOrderModel.getInstance().findByOrderId(orderDetail.orderID__c);
        orderDetail.orderID__r = order;

        String uniqueKey = String.valueOf(orderDetail.orderID__c) + '|'
                + orderDetail.orderLine__c;
        mockedRecordMap.put(uniqueKey, orderDetail);
    }
}

Test Classes

The test classes essentially remain the same. 

@isTest
private class ExternalOrderModelTest {
    @isTest static void testFindByOrderId() {
        createTestData();
        Test.startTest();
            Orders__x order = ExternalOrderModel.getInstance().findByOrderId('1');
            System.assertEquals(order.ExternalId, '123');
            System.assertEquals(order.OrderID__c, 1);
            System.assertEquals(order.CustomerID__c, '123');
        Test.stopTest();
    }

    @isTest static void testFindOrdersByCustomerId() {
        createTestData();
        Test.startTest();
        List<Orders__x> orderList = ExternalOrderModel.getInstance().findOrdersByCustomerId('123');
        System.assertEquals(orderList.size(), 3);
        Test.stopTest();
    }

    private static void createTestData() {
        //Create Account
        Account acct = new Account(
                name= 'Test Acct',
                Customer_ID__c = '123'
        );
        insert acct;

        //Create Order Data
        Orders__x order1 = new Orders__x(
                ExternalId = '123',
                OrderID__c  = 1,
                CustomerID__c = '123',
                CustomerID__r = acct,
                orderDate__c = Date.today().addDays(-5),
                shippedDate__c = Date.today().addDays(-3)
        );
        Orders__x order2 = new Orders__x(
                ExternalId = '124',
                OrderID__c  = 2,
                CustomerID__c = '123',
                CustomerID__r = acct,
                orderDate__c = Date.today().addDays(-5),
                shippedDate__c = Date.today().addDays(-3)
        );
        Orders__x order3 = new Orders__x(
                ExternalId = '125',
                OrderID__c  = 3,
                CustomerID__c = '123',
                CustomerID__r = acct,
                orderDate__c = Date.today().addDays(-5),
                shippedDate__c = Date.today().addDays(-3)
        );
        List<Orders__x> orderList = new List<Orders__x>{order1, order2};

        ExternalOrderModel.getInstance().addTestRecords(orderList);
        ExternalOrderModel.getInstance().addTestRecord(order3);
    }
}

@isTest
private class ExternalOrderDetailModelTest {
    @isTest static void testGetOrderDetails() {
        createTestData();
        Test.startTest();
            List<OrderDetails__x> orderDetailsForOrder1 =
                    ExternalOrderDetailModel.getInstance().getOrderDetails('1');
            List<OrderDetails__x> orderDetailsForOrder2 =
                    ExternalOrderDetailModel.getInstance().getOrderDetails('2');

            //Test OrderDetails
            System.assertEquals(orderDetailsForOrder1.size(), 1);
            System.assertEquals(orderDetailsForOrder1[0].product__c, 'Test Product 1');
            System.assertEquals(orderDetailsForOrder2.size(), 1);
            System.assertEquals(orderDetailsForOrder2[0].product__c, 'Test Product 2');
        Test.stopTest();
    }

    private static void createTestData() {
        //Create Account
        Account acct = new Account(
                name = 'Test Acct',
                Customer_ID__c = '123'
        );
        insert acct;

        //Create Order Data
        Orders__x order1 = new Orders__x(
                ExternalId = '123',
                OrderID__c = 1,
                CustomerID__c = '123',
                CustomerID__r = acct,
                orderDate__c = Date.today().addDays(-5),
                shippedDate__c = Date.today().addDays(-3)
        );
        Orders__x order2 = new Orders__x(
                ExternalId = '124',
                OrderID__c = 2,
                CustomerID__c = '123',
                CustomerID__r = acct,
                orderDate__c = Date.today().addDays(-5),
                shippedDate__c = Date.today().addDays(-3)
        );
        List<Orders__x> orderList = new List<Orders__x>{
                order1, order2
        };
        ExternalOrderModel.getInstance().addTestRecords(orderList);

        //Create Order Details
        OrderDetails__x orderDetail1 = new OrderDetails__x(
                orderID__c = String.valueOf(order1.orderID__c),
                orderLine__c = 1,
                product__c = 'Test Product 1',
                quantity__c = 3,
                unitPrice__c = 10
        );
        OrderDetails__x orderDetail2 = new OrderDetails__x(
                orderID__c = String.valueOf(order2.orderID__c),
                orderLine__c = 1,
                product__c = 'Test Product 2',
                quantity__c = 3,
                unitPrice__c = 10
        );
        List<OrderDetails__x> orderDetailList = new List<OrderDetails__x>{
                orderDetail1, orderDetail2
        };
        ExternalOrderDetailModel.getInstance().addTestRecords(orderDetailList);
    }

}

Conclusion

Making the ExternalObjectModelBase even more abstract and adding safeguards from usage outside unit testing makes this pattern even more usable for working with External objects. It also allows more control to the concrete models on how they want to create and store/index mocked records for facilitating access in unit-tested functions.

0 views0 comments

Comments


bottom of page