Do you want to better understand how custom metadata types behave in Apex tests, why they behave that way, or how you can write effective tests for them? If so, here’s the detail you’ve been waiting for! In this post, I’ll dig down into the nitty-gritty of testing applications that use custom metadata types.
If, on the other hand, you don’t know what a custom metadata type is, you should check out this post by my colleague Aaron Slettehaugh. The really-quick summary: Custom metadata types are the app configuration engine for Force.com.
What’s a Configuration?
What do we mean when we say that custom metadata types constitute an app configuration engine? What’s an app configuration, anyway?
“An app configuration” can mean a lot of things, but here we mean any bit of declarative metadata that’s a part of your application or that can be used to customize your application’s behavior. You’re probably familiar with a bunch of standard metadata types that have been part of Salesforce app configuration for a long time—things like:
- Activities settings
- Custom tabs
- Workflow rules
- Custom object definitions
These metadata types range from a set of simple switches to some fairly complex objects with extensive functionality behind them. What they all have in common, though, is that none of them have records that are business data; they’re all either part of your application or part of how an application is set up in your organization.
Although custom metadata types don’t yet have the power of all the metadata types above, the idea is the same. A custom metadata type is a new sort of setup object that you define. Records of that custom metadata type become part of your application, or, at the least, part of the way your application is configured.
Custom Metadata Types and SeeAllData
By default, Apex tests don’t see any data that they don’t create themselves. But as that topic states, there are exceptions: metadata objects and objects used to manage your organization. You wouldn’t want behavior in a test to depend on the existence of any particular account in your organization, but you probably would want it to depend on the existence of, for example, particular workflow rules. If you can’t write a test that can see your organization’s real configuration, you can’t really test that configuration.
Of course, if you want to see existing accounts in your tests, you have the option of doing so with the
@IsTest(SeeAllData=true) annotation. But this is considered bad practice for a variety of reasons, not the least of which is the problems it causes for application lifecycle management and (for ISVs) distribution: Your accounts don’t get copied from your sandbox to production or included in your package, so a test that relies on existing account data won’t transport very well.
Since custom metadata records are part of application configuration, and since they can be included in changesets and packages, we made them work like workflow rules, not like accounts. Apex tests see them, even when they aren’t using
@IsTest(SeeAllData=true). This is a request we saw often for custom settings, and for purposes of testing a configured application, it’s exactly what you want.
This is a big advantage of custom metadata types over using custom objects or custom settings to store application configurations. For example, suppose you were building an application using the Picklists R Us package in the sample application from the Custom Metadata Types Chatter group. If you write tests for the application you build, your tests need to be able to use the application’s real picklists, just as they need to be able to use the application’s real workflow rules.
Testing Across Configurations
As useful as this behavior is for testing fully-configured applications, it creates a challenge when what you’re trying to test is instead a range of possible application configurations.
This isn’t a challenge unique to custom metadata types; rather it’s a challenge with testing any code that needs to integrate with declarative metadata if you don’t yet know what that metadata is. For example, suppose you write an application that makes extensive use of SObject describe results, and that is meant to be used with a variety of custom objects. You can’t create custom objects and fields inside tests, and you can’t entirely hide custom objects and fields from tests. So testing the full range of possible custom objects is challenging. But because a lot of use cases for custom metadata types (especially for ISVs) involve just this sort of code, it’s worth talking about here.
At some point—and I want to emphasize that this comes with a Safe Harbor disclaimer, because this is a very forward-looking statement—we hope to provide some sort of a simulation mechanism for custom metadata in tests. If we do this, you’ll be able to declare that a particular test should see some fake set of custom metadata records for a type, rather than looking at the organization’s actual configuration.
For now, you should test this sort of code the same way you test other sorts of dynamic Apex: Ensure you have a wide range of configuration possibilities represented in actual metadata, even if that means creating some metadata for testing purposes only. For example, in the sample application mentioned previously, the “Picklists R Us” package contains both alphabetically sorted and non-alphabetically sorted picklists (called
TestPicklistNonAlpha) and a custom object (
PicklistTestData__c) that uses these picklists, and simply puts access to the object within a special permission set only granted to the test user. The Apex tests in the sample application check the way the framework behaves for that specific object.
Testing Global Custom Metadata
In the sample application referred to above, a reusable picklist only has an effect on an object related to it via a picklist usage. By introducing a testing-only object and relating the test picklists to that object only, Picklists R Us can perform its tests without having any effect on anyone’s production application. Other custom metadata types may be more global in their application. For example, you might use a custom metadata type to represent a single static list consulted in many places throughout your application. If this is the case, you might be wary of creating test records, as they will show up in the list.
You can still write tests for a case like this. All you need to do is add an extra field to your custom metadata type to indicate what sort of tests the type is active for. For example, you could add a Text field called
TestCase__c to your type. You can leave this field blank for production custom metadata and set it for test custom metadata.
Then, you can create a simple class to statically store a test case value:
When you query for your custom metadata in your application’s code, include TestCase__c in the WHERE clause of the query:
TestContext.testCase will be null, so this query will return real results. In tests, however, you can set the static variable to the test case you want, and retrieve exactly the correct records.
Don’t Overuse Test-Only Configurations
As I said above, what you most frequently need to test is a fully configured application, with its custom metadata in place. This is true for most enterprises and any partners using protected custom metadata types (since package installers cannot create additional records of those types).
If that describes you, by all means just test your real application configuration and skip all of these test-only custom metadata records and static variables. You wouldn’t create special “test-only” custom objects to test your dynamic SOQL unless your regular custom objects (those used by your application in production) were inadequate for your testing purposes; don’t do that with custom metadata either.
But if you do need to test a wider range of configurations than your application will actually include, these techniques should help you do it. Happy testing!