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');
}
}