-1

Can you help us with a test class? We are using the class in a LWC and we confirmed that it works when we test it manually. The coverage test stops at around at line 9 after the json serialize lines.

The error is: System.NullPointerException: Attempt to de-reference a null object Class.AssetsQuery.getAssetsOwnedForEntList: line 9, column 1 Class.AssetsAndEntitlementsQuery_Test.assetsTest: line 52, column 1

The log says: 14:10:19.129 (3033421382)|VARIABLE_SCOPE_BEGIN|[7]|fieldmapObj|Map|true|false 14:10:19.129 (3033433056)|VARIABLE_ASSIGNMENT|[7]|fieldmapObj|null|

Apex Class

public without sharing class AssetsQuery {
@AuraEnabled(cacheable=true)
public static Asset[] getAssetsOwnedForEntList(Id recordId, Object entitlementRec) {
System.debug('In getAssetsOwnedForEntList');
    system.debug('entitlementRec'+entitlementRec);
    Map<String,Object> mapObj = (Map<String,Object>)Json.deserializeUntyped(Json.serialize(entitlementRec));
    Map<String,Object> fieldmapObj = (Map<String,Object>)Json.deserializeUntyped(Json.serialize(mapObj.get('fields')));
    system.debug('mapObj'+mapObj);
    Map<String,Object> accfieldmap = (Map<String,Object>)fieldmapObj.get('AccountId');
    Map<String,Object> prodfieldmap = (Map<String,Object>)fieldmapObj.get('Product__c');
    system.debug('value'+accfieldmap.get('value'));
    String accId = (String)accfieldmap.get('value');
    String prodId = (String)prodfieldmap.get('value');

    return [
        SELECT Id, Name, Account.Name, Engine_Model__r.Name, Product2.Name, Operator_lookup__r.Name, Status
        FROM Asset
        WHERE AccountId = :accId AND (Engine_Model__c = :prodId OR Product2Id = :prodId) AND Status <> null
        ORDER BY Name ASC
    ];
}
}

Apex Test Class

@isTest(seeAllData=true)
Public class AssetsAndEntitlementsQuery_Test{

static testMethod void assetsTest(){
    Map<String,String> mprecordProduct=new Map<String,String>();
    FOR(RecordType rt:[SELECT Id, Name FROM RecordType WHERE SobjectType = 'Product2']) {
        mprecordProduct.put(rt.Name,rt.id);
    }

    Test.startTest();

    Product2 prd1 = new Product2();
    prd1.Name='CF34-10A';
    prd1.RecordTypeId=mprecordProduct.get('Engine Model');
    prd1.IsActive=true;
    Insert prd1;

    Account acc1 = new Account();
    acc1.name = 'LMNOP Airways';
    acc1.Account_Type__c  = 'CEO - Standard';
    Insert acc1;

    Entitlement ET1 = New Entitlement();
    ET1.Name='GTA LMNOP Airways';
    ET1.Comments__c='Third Parties';
    ET1.GTA_Number__c='12-12-12';
    ET1.StartDate=System.Today();
    ET1.GTA_Type__c='First Tier';
    ET1.AccountId=acc1.Id;
    ET1.Product__c=prd1.Id;
    ET1.EndDate=System.today()+365;
    Insert ET1;

    Asset as1 = New Asset();
    as1.Name='LMNOP 123';
    as1.AccountId=acc1.Id;
    as1.Operator_lookup__c=acc1.Id;
    as1.Product2Id=prd1.Id;
    as1.Engine_Model__c=prd1.Id;
    as1.Status='In Operation';
    Insert as1;

    AssetsQuery.getAssetsOwnedForEntList(ET1.Id,ET1);

    Test.stopTest();
}

}

js LWC

import { LightningElement, api, wire, track } from 'lwc';
import { getRecord,getFieldValue  } from 'lightning/uiRecordApi';
import getAssetsOwnedForEntList from 
'@salesforce/apex/AssetsQuery.getAssetsOwnedForEntList';
import getAssetsOperForEntList from  
'@salesforce/apex/AssetsQuery.getAssetsOperForEntList';
import PRODUCT_ID_FIELD from '@salesforce/schema/Entitlement.Product__c';
import ACCOUNT_ID_FIELD from '@salesforce/schema/Entitlement.AccountId';

export default class AssetsOnEntitlements extends LightningElement {
@api recordId;

@wire(getRecord, { recordId: '$recordId', fields: [PRODUCT_ID_FIELD, 
ACCOUNT_ID_FIELD] })
entitlementRecord;

@wire(getAssetsOwnedForEntList,{recordId: '$recordId', 
    entitlementRec: '$entitlementRecord.data'
})assetsOwnedEntitlementsData;

@wire(getAssetsOperForEntList,{recordId: '$recordId', 
entitlementRec: '$entitlementRecord.data'
})assetsOperEntitlementsData;

};

html lwc

Assets Owned

                    ></c-asset-On-Entitlement-Item>
                </template>
            </template>
            <template if:false={assetsOwnedEntitlementsData.data.length}>
                No matching Assets found for the same Account and Product
            </template>
        </template>
        <template if:true={errors}>
            An error has occurred while retrieveing the Assets:
            {errors}
        </template>
    </div>
</lightning-card>

<lightning-card icon-name="standard:product">
    <h1 slot="title">Assets Operated</h1>
    <div class="slds-m-horizontal_medium">
        <template if:true={assetsOperEntitlementsData.data}>
            <template if:true={assetsOperEntitlementsData.data}>
                <template
                    for:each={assetsOperEntitlementsData.data}
                    for:item="asset"
                >
                    <c-asset-On-Entitlement-Item
                        key={asset.Id}
                        asset={asset}

                    ></c-asset-On-Entitlement-Item>
                </template>
            </template>
            <template if:false={assetsOperEntitlementsData.data.length}>
                No matching Assets found for the same Account and Product
            </template>
        </template>
        <template if:true={errors}>
            An error has occurred while retrieveing the Assets:
            {errors}
        </template>
    </div>
</lightning-card>
  </template>

xml lwc

    <?xml version="1.0" encoding="UTF-8"?>
   <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>47.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Assets on Entitlements</masterLabel>
<targets>
    <target>lightning__RecordPage</target>
    <target>lightningCommunity__Page</target>
    <target>lightningCommunity__Default</target>
</targets>
<targetConfigs>
    <targetConfig targets="lightning__RecordPage">
        <objects>
            <object>Entitlement</object>
        </objects>
    </targetConfig>
    <targetConfig targets="lightningCommunity__Default">
        <property name="recordId" type="String" label="Record Id" default="{!recordId}"/>
    </targetConfig>
</targetConfigs>

  • What do your System.debug() statements tell you about which value is null? This is an exception you can almost always solve just by tracing the null value through your code. – David Reed Apr 5 at 21:45
  • @DavidReed My colleague has already confirmed the class works when we tested the LWC manually. Unfortunately, when I use this code in the execute anonymous window I can't get it to run. What am I doing wrong?AssetsQuery.getAssetsOwnedForEntList(5501B000001kdLh,Asset[]); – J.Ro Apr 5 at 21:49
  • Please edit your question to add information. It's not clear that Anonymous Apex is relevant to this test class issue. – David Reed Apr 5 at 21:50
  • @DavidReed I'm using execute anonymous window to test for the system.debug values. Per your message above, you said I need to know what null is. Do you have a better way of finding null other then running the code? Do you know if I should be executing something different in the window AssetsQuery.getAssetsOwnedForEntList(5501B000001kdLh,Asset[]); – J.Ro Apr 5 at 21:55
  • 1
    Look at the debug logs from your test run. – David Reed Apr 5 at 22:08
1

Let's break this down a little here:

1: first you should be checking if you even supplied a non-null object for entitlementRec

Map<String,Object> mapObj = (Map<String,Object>)Json.deserializeUntyped(Json.serialize(entitlementRec));

2: then you should be checking if you even have a mapObj and if you even have the keys for fields before you attempt to deserialize that:

Map<String,Object> fieldmapObj = (Map<String,Object>)Json.deserializeUntyped(Json.serialize(mapObj.get('fields')));

3: You can make this a bit more efficient, and clean by also casting this 1 time and checking and pulling keys.

Map<String,Object> accfieldmap = (Map<String,Object>)fieldmapObj.get('AccountId');

Map<String,Object> prodfieldmap = (Map<String,Object>)fieldmapObj.get('Product__c');

SO a more efficient and better way to do this while ensuring that you will not get null reference errors, as these are a sign that the code is not well written and not good for your users to see, would look more like this:

public without sharing class AssetsQuery {

    @AuraEnabled(cacheable=true)
    public static List<Asset> getAssetsOwnedForEntList(Id recordId, Object entitlementRec) {
        List<Asset> results = new List<Asset>();

        if( entitlementRec != null ){
            try {
                Object obj = JSON.deserializeUntyped( JSON.serialize( entitlementRec ));

                Map<String, Object> mapObj = obj != null
                    ? ( Map<String, Object> ) obj
                    : new Map<String, Object>();
                Map<String, Object> fieldMapObj = mapObj.containsKey( 'fields' ) 
                    ? (Map<String, Object> ) JSON.deserializeUntyped( JSON.serialize( mapObj.get( 'fields' )) 
                    : new Map<String, Object>();
                Map<String, Object> acctFieldMap = fieldMapObj.containsKey( 'AccountId' ) 
                    ? ( Map<String, Object> ) fieldMapObj.get( 'AccountId' )
                    : new Map<String, Object>();
                Map<String, Object> prodFieldMap = fieldMapObj.containsKey( 'Product__c' )
                    ? ( Map<String, Object> ) fieldMapObj.get( 'Product__c' )
                    : new Map<String, Object>();

                String accId = acctFieldMap.containsKey( 'value' ) && acctFieldMap.get( 'value' ) != null
                    ? String.valueOf( acctFieldMap.get( 'value' ))
                    : '';
                String prodId = prodFieldMap.containsKey( 'value' ) && prodFieldMap.get( 'value' ) != null
                    ? String.valueOf( prodFieldMap.get( 'value' ))
                    : '';

                results = [
                    SELECT Id, Name, Account.Name, Engine_Model__r.Name, Product2.Name, Operator_lookup__r.Name, Status
                    FROM Asset
                    WHERE AccountId = :accId AND (Engine_Model__c = :prodId OR Product2Id = :prodId) AND Status <> null
                    ORDER BY Name ASC
                ];
            }
            catch( Exception ex ){
                //... do some error handling
            }

            return results;
        }

    }
}

with this solution, you will not get any null pointer errors, and you can also determine what you need to do with any other errors you may encounter. I do prefer that you anticipate the errors you will receive versus the Pokemon Catch but for example purposes, there is a Pokemon Catch here. "" also in your test, be sure that you are providing it with an object in your method invocation. I would suggest testing with null, assert a result, and testing with a valid object as well. You should test in both cases as you want the behavior to be predictable.

| improve this answer | |
  • Thanks for the feedback! The apex class is passing the correct values when called by the LWC. The test class is giving us the null pointer error. Do you see any issues with the test class? – J.Ro Apr 6 at 1:44
  • looks like you test class is passing an SObject, not an Object. but the null pointer issue is the fact that you are not actually doing any good validations. how about asserting that the values you are expecting to pass in are what you are expecing them to be first. LWC will pass in an object, your Apex will pass in a different type here (SObject) so there is that difference. – Ronnie Apr 6 at 1:47
  • okay I'll fix the apex class to coincide with what you posted. Thanks again that is super helpful!... I'm curious now though- what do you mean it's passing an sObject instead of an Object? So this syntax isn't correct?:"Asset as1 = New Asset();" sorry, for so many questions I tried to research online but everything is referenced as an sObject – J.Ro Apr 6 at 2:19
  • JavaScript doesn't know what an SObject is. It only knows primitive types unless there is a declared type. So LWC sees an SObject as a primitive Object. Apex doesn't. I can only assume you're passing back values pulled from uiRecordApi which would explain the keys you're using in the Apex. But I really think if this is the case, it can be done better from how you are using the Apex with the LWC. Because it works doesn't mean it is correct or sustainable. – Ronnie Apr 6 at 3:38
  • I understand-unfortunately the class was given to us by Salesforce support and we aren't a team of developers so we were just trying to make it work. I'll make the updates you mentioned and research more on how to get around the sobject vs. primitive object issue in the test class. Thanks again for your help! – J.Ro Apr 6 at 3:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.