Avenga’s response to the war on Ukraine: Business Continuity and Humanitarian Aid
The Avenga Team
Table of content
Many applications use configuration data. Configuration data might be relevant to the entire organization, or subset of users or might be different for each user. In other words, it is possible to define some global configuration relevant to the whole organization or some minor configuration which is relevant to some small departments. In this article we will focus only on global configuration settings.
There are different possible solutions for storing configuration data. Configuration data can be stored in
Custom objects are not usually used for storage of configuration data but in some extreme edge-cases scenarios like if there is a need to have an encrypted text field, custom objects might be also used for configuration data since neither custom settings nor custom metadata support encrypted fields feature.
In most cases, however, custom settings or custom metadata are used.
Custom Metadata is a feature delivered back in Summer ‘15 release. It is similar to (Deprecated) List Custom Settings but it has several key differences. List Custom settings are deprecated since Spring ’18 release and custom metadata functionality should be used instead of them.
Usually Custom Metadata records are very attractive to developers since they can be deployed.
So it is also better to use Custom Metadata configuration than Custom Settings configuration since Custom Metadata records can be deployed and during migration some default settings might be migrated from one organization to another with metadata itself. Since Spring ’18 List Custom Settings are deprecated, so Custom Metadata became the default option.
However, the fact that custom metadata records cannot be constructed or inserted in Apex code brings obstacles to the process of writing unit tests to cover business logic depending on the custom metadata record values. Let’s discuss this more deeply considering the following example.
Assume we need to build an application for bank interest rates calculation.
Some banks might use simple interest rate calculation, another might use compound interest rate calculation.
We might be interested in building an application which would allow us to use any of these two calculations based on configuration.
Assume we have created a custom metadata type Custom_Metadata__mdt and created a picklist field on it Custom_Field__c and created a custom metadata record with DeveloperName value Default which we are going to use to determine which calculation to use.
Assume we created some simple class BankInterestCalculationLogic with the following code We might actually employ a strategy pattern to decide which formula to use for interest rate calculation, but for sake of simplicity let’s consider this piece of code. Anyway, even using a strategy pattern we would have some method to determine which strategy to use.
The main focus here is how to write unit tests which would use different strategies based on current value in the custom metadata record.
Simplest try to write test class for this might look like following This test class will succeed both in cases when the Custom_Field__c field value in the record is 1 or 2 but will fail with error
System.QueryException: List has no rows for assignment to SObjectif someone deletes this custom metadata record.
System.QueryException: List has no rows for assignment to SObject
However, the main problem is one of the branches of conditional statement will not be covered. If the value is 1 then the branch implementing the compound interest rate calculation will not be covered. Otherwise if the value is 2 then the branch implementing the simple interest rate would not be calculated.
Unfortunately official documentation doesn’t provide a solution how 100% of coverage can be achieved in this case.
We can’t insert custom metadata records (despite the fact that users may play with them and delete and modify the data in the custom metadata record).
We can’t even construct a custom metadata record to use it inside Test.loadData.
However, there is a solution to this problem.
We might overcome this obstacle by using JSON.deserialize method.
Let’s generate a class to deal with Custom Metadata records and basic test for it
Basic test gives 100% coverage for the CustomMetadataDAO class and also provides a utility method to set custom metadata records for the tests.
Look how can we use it to get 100% coverage to cover both interest rates calculations.
Let’s refactor our BankInterestCalculationLogic class like following
BankInterestCalculationLogic class like following and test like following Voila! Now we have 100% coverage and test contexts for different logic branches are separated!