13

I have written a class to calculate business hours for Case age in business hours. The class works great my issue is when trying to write decent test code for the class.

In following testing best practices, I want to create all my own test data and not rely on existing data (seeAllDate=true). I would assume that in order to test my different scenarios I would need to create and insert new business hours. Normally, I would use something like the snippet below

BusinessHours newHours = new BusinessHours(
    MondayStartTime = Time.newInstance(9, 0, 0, 0),
    MondayEndTime = Time.newInstance(17, 0, 0, 0),
    TuesdayStartTime = Time.newInstance(9, 0, 0, 0),
    TuesdayEndTime = Time.newInstance(17, 0, 0, 0),
    WednesdayStartTime = Time.newInstance(9, 0, 0, 0),
    WednesdayEndTime = Time.newInstance(17, 0, 0, 0),
    ThursdayStartTime = Time.newInstance(9, 0, 0, 0),
    ThursdayEndTime = Time.newInstance(17, 0, 0, 0),
    FridayStartTime = Time.newInstance(9, 0, 0, 0),
    FridayEndTime = Time.newInstance(17, 0, 0, 0),
    IsActive = true,
    IsDefault = true
);
insert newHours;

However, you cannot perform DML on business hours. Is there a way to do this without having to use seeAllData=true?

I have searched and searched but can;t find much info on using the business hours class in test code. Any help is much appreciated.

1
  • one could, of course use a Selector layer and ApexMocks to mock the selector for unit testing various combinations of business hours
    – cropredy
    Feb 27, 2018 at 2:13

3 Answers 3

16

You don't need to use @SeeAllData=true to be able to access the existing BusinessHours, as this is organization data rather than user data. This isn't that well documented - the concept is covered at :

https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_testing_data_access.htm

but BusinessHours isn't in the list.

To confirm this I've created a simple test class in one of my dev orgs that has a bunch of accounts and a single BusinessHours record:

@IsTest
private class BHTest {
    static void TestBH()
    {
        List<BusinessHours> bhs=[select id from BusinessHours where IsDefault=true];
        System.assert(bhs.size()==1);
        List<Account> accs=[select id from Account];
        System.assert(accs.size()==0);
    }
}

Executing this test confirms that I can see the BusinessHours records, but I can't see any accounts as they are isolated from my test.

Unfortunately you still have to rely on the BusinessHours record being present in the Salesforce instance your tests are running in.

2
  • 1
    Excellent. Thanks for this Bob. I have confirmed the behavior with a few tests of my own. You are correct as it is not at all well documented. Very good info to know. Thanks so much for the knowledge. Apr 25, 2014 at 11:10
  • If you found this answer, you probably want to vote for this Idea to support DML on Business hours: success.salesforce.com/ideaView?id=08730000000l59QAAQ Dec 13, 2016 at 21:36
0
  1. You can get coverage and test your scenarios implementing the StubProvider (https://developer.salesforce.com/docs/atlas.en-us.224.0.apexcode.meta/apexcode/apex_testing_stub_api.htm).

  2. Working with Salesforce DX and loading metadata, BusinessHours from source, you can test various scenarios that way (BusinessHours.settings-meta.xml). Everything loaded from source will be available to your unit tests by querying BusinessHours.

0

I had a scenario where I was using the isWithin() method of the BusinessHours class. Since Salesforce does not provide us with a way to programmatically set the BusinessHours within our unit test code I resorted to mocking BusinessHours class methods using StubProvider.

  1. Access BusinessHours methods through a wrapper class:
public class BusinessHoursWrapper {
    public Boolean isWithin(Id hoursId, Datetime dt) {
        return BusinessHours.isWithin(hoursId, dt);
    }
}
  1. Now you can access the isWithin method via the wrapper class:
public with sharing class MyExampleClass {

@TestVisible
private static BusinessHoursWrapper bhWrapper = new BusinessHoursWrapper();

// Find how many business days are within an interval from today.
private Integer myExampleMethod(Integer days) {
    Integer businessDaysWithinInterval = 0;

    BusinessHours hours = [SELECT Id FROM BusinessHours WHERE IsDefault = TRUE];

    Date today = Date.today();
    Datetime startDt = (Datetime) today;
    
    for (Integer i = 0; i < days; i++) {
        // Start counting from the day after today.
        startDt = startDt.addDays(1);
        if (businessHoursWrapper.isWithin(hours.Id, startDt)) {
            businessDaysWithinInterval++;
        }
    }
    return businessDaysWithinInterval;
}
  1. Mock the wrapper class:
@isTest
public with sharing class BusinessHoursMock implements StubProvider {
    
    Map<String, Boolean> isWithinValues = new Map<String, Boolean>();
  
    public BusinessHoursMock(Map<String, Boolean> isWithinValues){
       this.isWithinValues = isWithinValues;
    }
  
    public Object handleMethodCall(Object stubbedObject, String stubbedMethodName, System.Type returnType, List<System.Type> listOfParamTypes, List<String> listOfParamNames, List<Object> listOfArgs){  

        if(stubbedMethodName == 'isWithin') {
            Datetime startDate = (Datetime)listOfArgs.get(1);
            String startDay = startDate.format('EEEE');

            return isWithinValues.get(startDay);
        }
       
       return null;
    }
 }
  1. Use the mock in your unit test code:
@isTest
private class MyTestClass {

    private static Map<String, Boolean> isWithinValues = new Map<String, Boolean> {
        'Saturday' => false,
        'Sunday' => false,
        'Monday' => true,
        'Tuesday' => true,
        'Wednesday' => true,
        'Thursday' => true,
        'Friday' => true
    };

    @isTest
    private static void myTestMethod() {

        MyExampleClass.bhWrapper = (BusinessHoursWrapper) Test.createStub(BusinessHoursWrapper.class, new BusinessHoursMock(isWithinValues));

        Test.startTest();
        MyExampleClass myClass = MyExampleClass();
        Integer businessDaysInRange = myClass.myExampleMethod(4);
        Test.stopTest();

        Assert.areEqual(2, businessDaysInRange, 'Incorrect business days returned.');
    }
}

You must log in to answer this question.

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