6

Our Apex unit (true integration) test run takes about an hour to complete (260+ individual unit tests) and this really slows down our dev process as we are trying to adhere to Continuous Integration/Delivery principles and the "fast feedback" mantra so we know as soon as possible when we've introduced a regression into our codebase. I've been attempting to implement some suggestions I've seen here on StackExchange and the Salesforce community in general by using constructor dependency injection to build true unit tests that run quickly (using DML'less test data).

My question is how should we go about mocking a SOQL query within a service class? I'm struggling with how to mock the query. I can use the Test.isRunningTest() method (see below) to check if a test is running but I want the "integration" test (with DML) to actually test the db query. Is there a way to structure the class so a true integration test (with DML) will execute the query while another true unit test (without DML) can bypass the query and pass in a record list to "mock" the data returned by the query?

Service class:

public class OpportunityContactRoleRequired implements OpportunityContactRoleRequiredInterface {

    private List<OpportunityContactRole> roles;

    //default constructor
    public OpportunityContactRoleRequired() {

    }

    //constructor used by test; pass in the role test data (in place of SOQL query)
    @testVisible private OpportunityContactRoleRequired(List<OpportunityContactRole> roles) {
        this.roles = roles;
    }

    // Method I'm attempting to test
    public void setPrimaryContactFlag(List<Opportunity> opportunities) {
        Map<Id, OpportunityContactRole> oppContactRoles = new Map<Id, OpportunityContactRole>();

        // Tests will skip this block of code.  Ideally would like integration test to execute while unit test will bypass 
        If (!Test.isRunningTest()) {
            roles = [select OpportunityId, IsPrimary, Role 
                  from OpportunityContactRole
                  where OpportunityContactRole.OpportunityId IN :opportunities];
        }

        // Puts the contact roles in the map with the opp ID as the key
        for (OpportunityContactRole ocr : roles) {
            oppContactRoles.put(ocr.OpportunityId,ocr);
        }

        // The actual business logic I'm trying to test.  Iterate through each opp and if opp has contact assigned set flag to true, otherwise set to false
        for (Opportunity opp : opportunities) {
            if (oppContactRoles.containsKey(opp.id)) {
                opp.Primary_Contact_Assigned__c = true;
            } else {
                opp.Primary_Contact_Assigned__c = false;
            }
        }
    }
}

Unit test:

@isTest
private class OpportunityContactRoleRequiredMockTest {

    public static testMethod void testPrimaryContactFlagSetToTrue(){

        Integer i = 0;
        Account account = TestFactory.buildTestAccount(i);

        // Create new opp
        Opportunity opp = TestFactory.buildTestPSOpp(i, account);
        opp.Primary_Contact_Assigned__c = false;

        // Create new contact
        Contact contact = TestFactory.buildTestPSContact(i, account);  

        // Add the contact to the opp
        OpportunityContactRole oppContact = new OpportunityContactRole(Opportunity = opp, Contact = contact);
        List<OpportunityContactRole> oppContacts = new List<OpportunityContactRole>();
        oppContacts.add(oppContact);

        OpportunityContactRoleRequired svc = new     OpportunityContactRoleRequired(oppContacts);
        List<Opportunity> opportunities = new List<Opportunity>();
        opportunities.add(opp);

        svc.setPrimaryContactFlag(opportunities);

        // Verify that the contact assigned flag is set to true
        System.assertEquals(true, opportunities[0].Primary_Contact_Assigned__c, 'Primary    Contact Assigned field not set to true');
    }
}

1 Answer 1

6

Your business logic is tightly coupled to data access. For more serious projects as yours seems to be, I usually abstract data access with patterns such as Data Access Object or Repository.

If you combine this with some kind of configurable Service Locator, you can achieve what you're trying to do. Here's a naive implementation of the service locator:

public class ServiceLocator {

    public enum AccessMode { DATABASE, MOCK }

    public static AccessMode Mode = AccessMode.DATABASE;

    public static AccountRepository() {
        if (Mode == AccessMode.DATABASE) {
            return new DatabaseAccountRepository();
        }
        else {
            return new MockAccountRepository();
        }
    }
}

An abstract factory is probably a better way to implement this, which would avoid all the if/else logic. The mode defaults to Database access. In your non-integration test classes, set the ServiceLocator's mode to MOCK, then run your tests.

You must log in to answer this question.

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