How to Use the 'forName' Method of the 'Type' Class to Verify the Presence of a Namespace When Running Test Methods in Different Development Environments

ליאור נכתב על ידי ליאור לביא, עודכן בתאריך 03/11/2023

When developing packages with namespaces, one of the issues to consider is the impact of using a namespace when working with strings to reference metadata components. On one hand, it's a strong point of dynamic code that can accept references to metadata components by their names. On the other hand, when we try to run our code in environments where our metadata doesn't automatically have the namespace prefix, such as scratch orgs without a namespace, our references to metadata using strings may lead to issues, like failing unit tests.

Let's take a look at the following example:

String recordString = '{"ACME__Custom_Field_1__c":"Some text","ACME__Custom_Field_2__c":"More text","ACME__Custom_Field_3__c":"Even more text!"}';
Custom_Metadata_Object__mdt record = (Custom_Metadata_Object__mdt) JSON.deserialize(recordString, Custom_Metadata_Object__mdt.class);

In the above example, we create a record for a metadata object named Custom_Metadata_Object__mdt. The record is created by calling the JSON.deserialize method with a string that represents the record in JSON format and the class of the type. Note that the JSON.deserialize method returns an Object, so we need to cast or explicitly convert it to Custom_Metadata_Object__mdt.

What's essential to notice in the example above is that the fields within the "recordString" all start with the "ACME" namespace. This example works correctly in environments where the default namespace is "ACME," like scratch orgs configured with the "ACME" namespace, or in environments without a namespace or with a different default namespace where a package with the "ACME" namespace has been installed, and the Custom_Metadata_Object__mdt object is included.

However, when we try to run this example in an environment where the namespace is different from "ACME," and we pushed or deployed it directly from our Visual Studio Code project without installing the package itself, we encounter a problem. Since the "ACME" namespace isn't defined in the environment, the record we created will be stored in runtime memory. But if we attempt to use it, we'll find ourselves with fields that don't really exist in the environment but are still defined as part of the record we created for the Custom_Metadata_Object__mdt object.

A simple way to prevent this situation from the outset is to use the JSON.deserializeStrict() method instead of JSON.deserialize. This method would fail at runtime and display an error message stating that fields with names that include the "ACME" namespace don't exist on the object.

The gap between the fields existing in the record we created at runtime and the fields that actually exist in the environment, which don't include the "ACME" namespace in their names, could cause our tests to fail.

You might ask yourself: Why are we dwelling so much on the case of tests related to namespace usage?
The answer is that one of the scenarios where namespace usage appears is when creating instances of records at runtime to simulate custom metadata records as part of using dependency injection for unit tests. If you're not familiar with the term "Dependency Injection" or not sure why it's required specifically when working with custom metadata for testing, don't worry! I plan to write a detailed post on this topic. In the meantime, you're welcome to continue reading the current post in case you encounter other scenarios where using strings to create records or reference fields or objects with a namespace causes you problems.

Solution

One way to address our issue is to use the Type.forName method to check if a type exists for a class or object under a specific namespace. I mention "class," but the method also works for custom metadata. For more reading on the Type class, its methods, and use cases, click here.

Example

String namespace = Type.forName('acme', 'Custom_Metadata_Object__mdt') != null ? 'ACME__' : '';
Custom_Metadata_Object__mdt record = (Custom_Metadata_Object__mdt) JSON.deserialize('{"' + namespace + 'Custom_Field_1__c":"Some text"}', Custom_Metadata_Object__mdt.class);

What the Type.forName method does is check if there is a type for a class or object under the "acme" namespace (note that the namespace should be in lowercase). If it exists, it stores the "ACME__" value in the "namespace" variable. If not, it stores an empty string. Then, we can use the "namespace" variable to determine whether the fields should contain the namespace prefix in our JSON string. This solution addresses the issue of checking if the environment contains our namespace or not.

That's all for this post! Creating metadata records at runtime may not be a topic that many Apex developers deal with daily, but I thought it's worth presenting since metadata and namespaces are central topics in package development and versioning. Using Type.forName is a particularly useful tool to solve an issue that can become very frustrating.