1

I was asked to put my team's project into 2GP Managed Package. This project is relatively big, so passing Validating metadata step during sfdx force:package:version:create was really a struggle.

Now, I need to pass the package version creation with --codecoverage flag turned on, in order to promote the package version to release.

The first issue I'm facing is that, in my code there are a lot of lines like below: Schema.getGlobalDescribe().get('Custom_Obj__c').getDescribe().fields.getMap().keySet())

It results in System.NullPointerException: Attempt to de-reference a null object.

Question 1: Is renaming the arguments to namespace__Custom_Obj__c the only option? Can it be handled in a better way? Because this way, I'm restricting my development, to work only in Managed Package approach.

The other issue is that, for every Apex Testing (which I perform during version creation with --codecoverage flag on) results I have to wait around 2 hours. This is ridiculous!

Question 2: Is there a better way to run my tests? How can I for example, check how just one test behaves in a managed package scenario? Without Managed Package approach, the tests are 100% successful and those NullPointerException are present only in MP approach.

I'm new to the 2GP MP world. Maybe I misunderstood some key concepts?

2
  • Use of Schema.getGlobalDescribe() is bad practice. See some more detail in this Q&A answer. If you have a hard coded string such as in your example, the code is plain wrong. Instead you should simply do SObjectType.Custom_Obj__c.fields.getMap().keySet(). You can selectively run tests during 2GP development just like you do outside 2GP development. However, you simply have to wait for version creation; it takes as long as it takes and you have no control.
    – Phil W
    Jul 20, 2022 at 13:28
  • Regarding your first question, I can suggest you retrieve Namespace prefix from organization and add it to custom object name like below: String LOCAL_NAMESPACE = [SELECT NamespacePrefix FROM Organization LIMIT 1].NamespacePrefix; and then Schema.getGlobalDescribe().get((String.isNotBlank(LOCAL_NAMESPACE) ? LOCAL_NAMESPACE : '') + 'Custom_Obj__c'). Jul 20, 2022 at 13:29

1 Answer 1

3

Is renaming the arguments to namespace__Custom_Obj__c the only option? Can it be handled in a better way? Because this way, I'm restricting my development, to work only in Managed Package approach.

Unless you're intentionally trying to keep the base object decoupled from the package, simply use:

sObjectType.Custom_Obj__c.fields.getMap().keySet();

If you are trying to keep the base object out of your package, use:

((sObject)Type.forName('Schema','Custom_Obj__c'))
  .newSObject().getSObjectType().getDescribe().fields.getMap().keySet();

The other issue is that, for every Apex Testing (which I perform during version creation with --codecoverage flag on) results I have to wait around 2 hours. This is ridiculous!

To be clear, Schema.getGlobalDescribe() can be very expensive and should only be used as a last resort (e.g. you need to list every object anyways). I've seen this method take up to 3 seconds on the first call, plus an additional second for each call thereafter.

Also, this is one example of why your tests are probably taking two hours. Tests run in serial mode only when testing code coverage this way, so if your tests are expensive, they will take a while to run. If you have a lot of smaller tests, they will likely take longer to run than fewer larger tests. That's just the nature of how transactions work in Salesforce.

Anecdotally, I once worked on a project that had a class with hundreds of "constants", and because of how they were working, that class, which was called from every other class, added a full 1 second of CPU time to every transaction, multiplied by the 500 or so unit tests we had to run. I refactored it so it lazy-loaded the data, and immediately reduced our deployment time by about 7 minutes. It also made every transaction in the database 1 second faster, which the users noticed. The point is, do some profiling and figure out how to optimize away your time killers.

Question 2: Is there a better way to run my tests? How can I for example, check how just one test behaves in a managed package scenario? Without Managed Package approach, the tests are 100% successful and those NullPointerException are present only in MP approach.

Properly written code won't have this problem. There are very few exceptions when a managed package and unmanaged code will behave differently, and most of the time, you can work around them by either checking your namespace or by using alternative methods.

I'm new to the 2GP MP world. Maybe I misunderstood some key concepts?

One important aspect is that you need to understand that everywhere you need namespaces, there are ways to get the code to behave correctly. Avoiding Schema.getGlobalDescribe() is one such example. Others include preferring to use tokens instead of strings (e.g. CustomObj__c c = new CustomObj__c() instead of CustomObj__c c = (CustomObj__c)Schema.getGlobalDescribe().get('CustomObj__c').newsObject()). Your code, if properly written, should be namespace agnostic.

Also, one final note: when you are developing, you can create a scratch org with the same namespace as your 2GP. See Link a Namespace to a Dev Hub Org for more information. This allows you to always work with a namespace, which provides you a more accurate way to determine why code is failing.

5
  • Take a look at the performance findings I had; the clever Type-based instantiation approach for schema access is actually less efficient (at least in the analysis I did on a scratch org) than Schema.describeSObjects with deferred handling.
    – Phil W
    Jul 20, 2022 at 13:52
  • Also note that the statement "everywhere you need namespaces, there are ways to get the code to behave correctly" has some caveats. For example, flows and Aura components have to include the namespace prefix.
    – Phil W
    Jul 20, 2022 at 13:54
  • 1
    @PhilW (1) Nice trick. Not worth switching from Type.forName in existing code, but nice to use going forward. (2) I did say "code." Flows aren't code as far as I'm concerned. Aura mostly handles namespaces for you. While this says otherwise, I've never intentionally used my namespace in any code; the API does it for you. I did it just now to doublecheck. And really, my final note was about Scratch Orgs. They're preferable to other types of orgs.
    – sfdcfox
    Jul 20, 2022 at 14:25
  • 2
    @PhilW Oh, and re: performance, I was mostly focused on avoiding the use of Schema.getGlobalDescribe. Nothing kills performance more than that method. Whether it's using Type.forName, sObjectType.ObjectName, or describeSObjects, they all beat getGlobalDescribe by a large margin.
    – sfdcfox
    Jul 20, 2022 at 14:27
  • When we introduced some Aura components to our main 1GP back in 2018 we had to switch to using namespaced orgs because Aura wasn't smart enough to consider "c:" as the current package namespace when trying to push the components onto the packaging org. The namespace reference is unavoidable in Visualforce LightningOut usage of the app and Aura components.
    – Phil W
    Jul 20, 2022 at 15:43

You must log in to answer this question.

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