My last post looked at using a wrapper class to make Custom Settings more convenient to access from apex code and to ease defaulting and unit testing.
If, like me, you’ve taken the leap to using Custom Metadata instead of List based Custom Settings you may have found yourself with a headache when writing your unit tests. Sure, you don’t need SeeAllData=true to see your metadata from your unit test but you may want to make sure the settings are in order for your given test.
Unlike Custom Settings, you can’t use DML to inject data for your unit tests. Attempting to instantiate a custom metadata object results in the rather friendly:
[OPERATION FAILED]: classes/MyClass.cls: Field is not writeable
To get round this I’ve adapted the pattern from my last post to support Custom Metadata.
Lets use the example Custom Metadata defined here:
Nothing too exciting, a checkbox and a text field but it’ll do to demonstrate.
So we’ll start with the wrapper:
/** * Wrapper for My_Custom_Metadata_Type__mdt to ease access and testing */ public with sharing class MyCustomMetadataTypes { // Cached Map of metadata @TestVisible private static Map s_myCustomMetadataTypeMap = new Map(); // Public getter public static Metadata getMyCustomMetadataType(String developerName) { // Check the cache Metadata myMetadata = s_myCustomMetadataTypeMap.get(developerName); // Not found, so query the metadata if (myMetadata == null) { My_Custom_Metadata_Type__mdt myCustomMetadataType; try { myCustomMetadataType = [Select DeveloperName, My_Checkbox__c, My_Text__c From My_Custom_Metadata_Type__mdt Where DeveloperName = :developerName]; } catch(System.QueryException sqe) { throw new MyCustomMetadataTypeException('Could not find My Custom Metadata Type with DeveloperName: ' + developerName); } // Wrap the metadata using the inner class myMetadata = new Metadata(myCustomMetadataType); // Add it to the cache s_myCustomMetadataTypeMap.put(myCustomMetadataType.DeveloperName, myMetadata); } // Return the wrapped metadata return myMetadata; } // Inner class to wrap the metadata public class Metadata { // Properties represent the Fields in the Metadata public Boolean My_Checkbox {get; set;} public String My_Text {get; set;} // Constructor used when creating test values public Metadata() { } // Construct from a concrete metadata instance private Metadata(My_Custom_Metadata_Type__mdt metadata) { this.My_Checkbox = metadata.My_Checkbox__c; this.My_Text = metadata.My_Text__c; } } public class MyCustomMetadataTypeException extends Exception {} }
You’ll notice we have an inner class that reflects the fields that we’ve defined in the Custom Metadata.
It has two constructors, a public one we can use for mocking test data and a private one for marshalling an instance of the metadata in the real world. We’ll always use this inner class to get metadata valus and never reference the metadata directly.
A static Map of Developer Names to instances of the inner class provide the way to set values for Unit Tests. It’s set to TestVisible for this purpose. It’s also used to cache values whenever the getter is called. While it’s not strictly necessary to cache the data as Salesforce takes care of that, it does help support the pattern.
The public getter simply takes an argument of a developer name and returns an instance of the inner class populated with the metadata. So in you’re production code you can do things like:
String myString = MyCustomMetadataTypes.getMyCustomMetadataType('indierock').My_Text;
If the developer name doesn’t exist it will throw an exception which you can catch.
I’ll now show you some unit test examples and we’re done:
@isTest private class MyCustomMetadataTypesTest { @isTest static void thisWontWork() { // You can't do this as the metadata fields are not writeable! // Result: [OPERATION FAILED]: classes/MyCustomMetadataTypesTest.cls: Field is not writeable: My_Custom_Metadata_Type__mdt.DeveloperName /* My_Custom_Metadata_Type__mdt metadata = new My_Custom_Metadata_Type__mdt( DeveloperName = 'test', My_Checkbox__c = true, My_Text__c = 'some text' ); insert metadata; */ } @isTest static void thisWorks() { // Create our test metadata { MyCustomMetadataTypes.Metadata metadata = new MyCustomMetadataTypes.Metadata(); metadata.My_Checkbox = true; metadata.My_Text = 'some text'; MyCustomMetadataTypes.s_myCustomMetadataTypeMap.put('test', metadata); } { MyCustomMetadataTypes.Metadata metadata = new MyCustomMetadataTypes.Metadata(); metadata.My_Checkbox = false; metadata.My_Text = 'some more text'; MyCustomMetadataTypes.s_myCustomMetadataTypeMap.put('test2', metadata); } // Use the wrapper to access the metadata MyCustomMetadataTypes.Metadata metadata = MyCustomMetadataTypes.getMyCustomMetadataType('test'); // Assert the results System.assertEquals(true, metadata.My_Checkbox); System.assertEquals('some text', metadata.My_Text); // Use the wrapper to access the metadata MyCustomMetadataTypes.Metadata metadata2 = MyCustomMetadataTypes.getMyCustomMetadataType('test2'); // Assert the results System.assertEquals(false, metadata2.My_Checkbox); System.assertEquals('some more text', metadata2.My_Text); // This demonstrates the exception thrown when the metadata developer name can't be found try { MyCustomMetadataTypes.Metadata metadata3 = MyCustomMetadataTypes.getMyCustomMetadataType('test3'); System.assert(false, 'Expected an Exception.'); } catch(Exception e) { System.assert(e instanceof MyCustomMetadataTypes.MyCustomMetadataTypeException); } } @isTest static void thisAlsoWorks() { // Using some Custom Metadata already defined in my org. Look, no SeeAllData=true! // Use the wrapper to access the metadata MyCustomMetadataTypes.Metadata metadata = MyCustomMetadataTypes.getMyCustomMetadataType('indierock'); // Assert the results System.assertEquals(true, metadata.My_Checkbox); System.assertEquals('The Cribs', metadata.My_Text); } }
[…] via Wrapping Custom Metadata to Support Unit Testing — melted:wires […]