Custom Action Plugin
Use the Custom Action Plugin to configure additional actions you want to trigger via Zuora Rules Engine. The Custom Action Plugin interface class, zqu.CustomActionPlugin
, contains the following interface methods:
perform
isUpdateAction
isAddRemoveAction
isValidateAction
Perform Method
In the perform
method, define the logic this custom action will execute. See the sample code below for an example implementation of the perform
method.
The method takes the following input parameters.
Input |
Data Type |
Description |
---|---|---|
masterObject | zqu.DataObject | Contains the record that the rule is currently running against and its related data. This will generally be a Quote record. |
attributes | Map<String, Object> | A map of parameters that can be defined by an admin when configuring a custom action. |
logs | String[] | A list of strings that get printed to the Rules Engine log when the rules engine executes the action. |
Rule Types Methods
When initiating the Rules Engine programmatically, you have an option to specify which types of actions to run. For example, calling the Rules Engine with the Rule Type of VALIDATION will only evaluate rules with a Validation action.
By default, the Rules Engine runs all actions.
When defining a custom action, set the following methods to return true
to specify the types of this action.
Method |
Rule Types that Can Invoke this Action |
---|---|
isUpdateAction() |
ALL or PRICE |
isAddRemoveAction() |
ALL or PRODUCT |
isValidateAction() |
ALL or VALIDATION |
Constructors
Using the following constructors, you can instantiate different types of DataObjects to add new data during a custom action.
Contructor | Description |
---|---|
new ZChargeGroupDataObject new ZChargeGroupDataObject |
Creates a new ChargeGroup, retrieved through the zqu.zQuoteUtil.getChargeGroup global method. QuoteRatePlan fields can be retrieved from this record, even if it has not yet been saved to the database. Charge objects can be retrieved from an instance of this object using: getChildren('zqu__QuoteRatePlanCharge__c') |
new PlaceholderChargeGroupDataObject |
Represents a charge group to be added after rules execution completes. Charge and Plan details are not accessible from this instance, but because it does not call getChargeGroups, it runs more quickly and makes no SOQL queries. |
new CommittedDataObject |
Represents an SObject record inserted into the Database. Not used for Product Actions. |
Sample Code
As of version 10.11, this Sample Code can be applied to CPQ 9 (legacy UI) and CPQ X (new Quote Studio UI) based on the conditional logic for X or 9 seen in the code below. In case you already have CPQ 9 configured, all you need to do is add the CPQ X conditional logic, and if you are only going to be using CPQ X, you can exclude the CPQ 9 conditions.
Additionally, if the unmanaged Custom Action Plugin class was created before version 10.11, the metadata of the class needs to be updated to version 10.11 or higher.
The below sample code implements a custom action that updates charges, removes and adds rate plans, and validates the quote.
global with sharing class MyCustomActionPlugin implements zqu.CustomActionPlugin { global void perform(zqu.DataObject masterObject, Map<String, Object> attributes, String[] logs) { String quoteId = (String) masterObject.get('Id'); String attributeMsg = (String) attributes.get('Application'); List <zqu.DataObject> plans = masterObject.getChildren('zqu__QuoteRatePlan__c'); List <zqu.DataObject> charges = masterObject.getChildren('zqu__QuoteRatePlanCharge__c'); Boolean isQuoteStudioProcess = false; for (zqu.DataObject plan : plans) { if (plan instanceof zqu.QPlanDataObject) { isQuoteStudioProcess = true; break; } } // Log Attributes. logs.add('Quote Id : ' + quoteId); logs.add('Attribute Message : ' + attributeMsg); logs.add('Is Quote Studio Process : ' + isQuoteStudioProcess); logs.add('Number Of Plans : ' + plans.size()); logs.add('Number Of Charges : ' + charges.size()); // ------------ ADD PRODUCT ACTION ------------ // //Note - Ensure that the Product Rate Plan Id corresponds to the product you wish to process.// ID productRatePlanIdToAdd = [select id from zqu__ProductRatePlan__c limit 1].id; zqu.DataObject toBeAddedPlan = null; // CASE 1 // toBeAddedPlan = new zqu.PlaceholderChargeGroupDataObject(productRatePlanIdToAdd); toBeAddedPlan.flagAdded(); masterObject.addChild('zqu__QuoteRatePlan__c', toBeAddedPlan); // CASE 2 // toBeAddedPlan = null; if (isQuoteStudioProcess) { // CPQ X Rules Engine Flow. toBeAddedPlan = new zqu.QPlanDataObject(quoteId, productRatePlanIdToAdd); } else { // CPQ 9 Rules Engine Flow. toBeAddedPlan = new zqu.ZChargeGroupDataObject(masterObject, productRatePlanIdToAdd); } zqu__ProductRatePlan__c prp = new zqu__ProductRatePlan__c(Id = productRatePlanIdToAdd); zqu.DataObject dataProductRatePlan = new zqu.CommittedDataObject(prp); toBeAddedPlan.putParent('zqu__ProductRatePlan__c', dataProductRatePlan); toBeAddedPlan.flagAdded(); masterObject.addChild('zqu__QuoteRatePlan__c', toBeAddedPlan); // ------------ UPDATE PRODUCT ACTION ------------ // Decimal chargeListPrice = 10, tierListPrice = 10, quantity = 20, tierDiscount = 50; for (zqu.DataObject charge : charges) { // Update the list price at the charge level for 'Flat Fee Pricing' charge type. if (charge.get('zqu__Model__c') == 'Flat Fee Pricing') { charge.put('zqu__ListPrice__c', chargeListPrice); logs.add('Updating list Price of (' + charge.get('Name') + ') to ' + chargeListPrice + '.'); chargeListPrice += 10; } // Update the list price at the charge level for 'Per Unit Pricing' charge type. if (charge.get('zqu__Model__c') == 'Per Unit Pricing') { charge.put('zqu__Quantity__c', quantity); logs.add('Updating quantity of (' + charge.get('Name') + ') to ' + quantity + '.'); quantity += 10; } // Update the tier details (List Price, Discount & Effective Price). if (charge.get('zqu__ChargeType__c') == 'Usage' && (charge.get('zqu__Model__c') == 'Tiered Pricing' || charge.get('zqu__Model__c') == 'Volume Pricing')) { // CPQ 9 Rules Engine Flow. if (!isQuoteStudioProcess) { zqu.ZChargeDataObject chargeObject = (zqu.ZChargeDataObject) charge; List<zqu__QuoteCharge_Tier__c> tiers = chargeObject.getCharge().chargeTiersObjects; if (tiers != null && tiers.size() > 0) { for (zqu__QuoteCharge_Tier__c tier : tiers) { tier.zqu__Price__c = tierListPrice; tier.zqu__Discount__c = tierDiscount; tier.zqu__Effective_Price__c = (tierListPrice / 2); tierListPrice += 10; } logs.add('Updating the tiers of charge (' + charge.get('Name') + ').'); // Populate the changed tier information. chargeObject.rePopulateCustomChargeTiers(tiers); } } // CPQ X Rules Engine Flow. if (isQuoteStudioProcess) { zqu.QChargeDataObject chargeObject = (zqu.QChargeDataObject) charge; if (chargeObject != null) { zqu.QCharge qc = chargeObject.getQCharge(); if (qc != null) { List<zqu.QTier> tiers = qc.getTiers(); if (tiers != null && tiers.size() > 0) { for (zqu.QTier tier : tiers) { tier.put('zqu__Price__c', tierListPrice); tier.put('zqu__Discount__c', tierDiscount); tier.put('zqu__Effective_Price__c', (tierListPrice / 2)); tierListPrice += 10; } // Update the tiers in the charge object. qc.setTiers(tiers); } } } } } } // ------------ REMOVE PRODUCT ACTION ------------ // String productToRemove = 'Annually Billed Per Unit Product'; for (zqu.DataObject plan : plans) { String productName = (String) plan.get('zqu__QuoteProductName__c'); System.debug('Product Name : ' + productName); if (productName == productToRemove) { logs.add('Product Found : ' + productName); // Mark As Removed. plan.flagRemoved(); logs.add('Removed : ' + productName); } } // ------------ VALIDATION ERROR ------------ // if (plans.size() > 2) { throw new zqu.CustomValidationException('You cannot have more than two plans on a quote.'); } } global Boolean isUpdateAction() { return true; } global Boolean isAddRemoveAction() { return true; } global Boolean isValidateAction() { return true; } }
Below is a sample code with a test class for the custom action plugin.
@isTest global class MyCustomActionPluginTest { public static final String DEFAULT_BILLTO_CONTACT_FIRST_NAME = 'Test Bill-To First Name'; public static final String DEFAULT_BILLTO_CONTACT_LAST_NAME = 'Test Bill-To Last Name'; public static final String DEFAULT_SOLDTO_CONTACT_FIRST_NAME = 'Test Sold-To First Name'; public static final String DEFAULT_SOLDTO_CONTACT_LAST_NAME = 'Test Sold-To Last Name'; public static final String DEFAULT_OPPORTUNITY_NAME = 'Test Opportunity Name'; public static final String DEFAULT_ACCOUNT_NAME = 'Test Salesforce Account Name'; public static final String DEFAULT_CURRENCY = 'USD'; public static final Date DEFAULT_EFFECTIVE_START_DATE = System.today(); public static final Date DEFAULT_EFFECTIVE_END_DATE = System.today().addYears(1); public static final String NEW_SUBSCRIPTION = 'New Subscription'; public static final String MOCK_ZUORA_PROD_ID = '1118e487366202430136757c960716b6'; @TestSetup private static void setupTestData(){ //create a test account Account testaccount = new Account(Name = DEFAULT_ACCOUNT_NAME); insert testaccount; system.assertEquals([SELECT count() from Account],1); //create billto,soldTo contacts Contact billToContact = new Contact(FirstName = DEFAULT_BILLTO_CONTACT_FIRST_NAME, LastName = DEFAULT_BILLTO_CONTACT_LAST_NAME); billToContact.accountId = testaccount.Id; insert billToContact; Contact soldToContact = new Contact(FirstName = DEFAULT_SOLDTO_CONTACT_FIRST_NAME, LastName = DEFAULT_SOLDTO_CONTACT_LAST_NAME); soldToContact.accountId = testaccount.Id; insert soldToContact; system.assertEquals([SELECT count() from Contact],2); //create an opportunity Opportunity testopportunity = new Opportunity(Name = DEFAULT_OPPORTUNITY_NAME, AccountId = testaccount.Id); testopportunity.StageName = 'Closed Won'; testopportunity.CloseDate = System.today(); insert testopportunity; system.assertEquals([SELECT count() from Opportunity],1); //create a zuora quote zqu__Quote__c quote = new zqu__Quote__c(); quote.zqu__Currency__c = DEFAULT_CURRENCY; quote.Name = 'Positive Quote'; quote.zqu__Account__c = testaccount.Id; quote.zqu__AutoRenew__c = true; quote.zqu__Amendment_Name__c = 'Positive Amendment'; quote.zqu__Opportunity__c = testopportunity.Id; quote.zqu__BillToContact__c = billToContact.Id; quote.zqu__SoldToContact__c = soldToContact.Id; quote.zqu__InitialTerm__c = 12.0; quote.zqu__RenewalTerm__c = 6.0; quote.zqu__PaymentMethod__c = 'Credit Card'; quote.zqu__ValidUntil__c = DEFAULT_EFFECTIVE_START_DATE; quote.zqu__StartDate__c = DEFAULT_EFFECTIVE_START_DATE; quote.zqu__SubscriptionTermStartDate__c = DEFAULT_EFFECTIVE_START_DATE; quote.zqu__SubscriptionTermEndDate__c = DEFAULT_EFFECTIVE_END_DATE; quote.zqu__SubscriptionType__c = NEW_SUBSCRIPTION; quote.zqu__BillingMethod__c = 'Both'; quote.zqu__Subscription_Term_Type__c = 'Termed'; insert quote; system.assertEquals([SELECT count() from zqu__Quote__c],1); //creating ZProducts zqu__ZProduct__c zuoraproduct = new zqu__ZProduct__c(); zuoraproduct.Name = 'CC Testing'; zuoraproduct.zqu__EffectiveStartDate__c = DEFAULT_EFFECTIVE_START_DATE; zuoraproduct.zqu__EffectiveEndDate__c = DEFAULT_EFFECTIVE_END_DATE; zuoraproduct.zqu__SKU__c = 'testingsku0001'; zuoraproduct.zqu__ZuoraId__c = 'zuoraid0000000001'; zuoraproduct.zqu__Deleted__c = false; insert zuoraproduct; system.assertEquals([SELECT count() from zqu__ZProduct__c],1); //creating salesforce product Product2 testsfproduct = new Product2(); testsfproduct.Name = 'CC Testing'; testsfproduct.zqu__EffectiveStartDate__c = DEFAULT_EFFECTIVE_START_DATE; testsfproduct.zqu__EffectiveEndDate__c = DEFAULT_EFFECTIVE_END_DATE; testsfproduct.zqu__SKU__c = 'testingsku0001'; testsfproduct.zqu__ZuoraId__c = 'zuoraid0000000001'; testsfproduct.zqu__Deleted__c = false; insert testsfproduct; system.assertEquals([SELECT count() from Product2],1); //creating Product rate plan zqu__ProductRatePlan__c testproductrateplan = new zqu__ProductRatePlan__c(); testproductrateplan.Name = 'Test Product Rate Plan'; testproductrateplan.zqu__EffectiveStartDate__c = DEFAULT_EFFECTIVE_START_DATE; testproductrateplan.zqu__EffectiveEndDate__c = DEFAULT_EFFECTIVE_END_DATE; testproductrateplan.zqu__Product__c = testsfproduct.Id; testproductrateplan.zqu__ZProduct__c = zuoraproduct.Id; testproductrateplan.zqu__ZuoraId__c = MOCK_ZUORA_PROD_ID; testproductrateplan.zqu__Deleted__c = false; testproductrateplan.zqu__ActiveCurrencies__c = DEFAULT_CURRENCY; insert testproductrateplan; system.assertEquals([SELECT count() from zqu__ProductRatePlan__c],1); //creating Product rate plan charge with Model as 'Flat Fee Pricing' and Type as 'Recurring' zqu__ProductRatePlanCharge__c productrateplancharge = new zqu__ProductRatePlanCharge__c(); productrateplancharge.Name = 'Test Product Rate Plan Charge'; productrateplancharge.zqu__Model__c = 'Flat Fee Pricing'; productrateplancharge.zqu__Type__c = 'Usage'; productrateplancharge.zqu__UOM__c = 'UOM tesing'; productrateplancharge.zqu__DefaultQuantity__c = 1; productrateplancharge.zqu__MinQuantity__c = 0; productrateplancharge.zqu__MaxQuantity__c = 500; productrateplancharge.zqu__RecurringPeriod__c = 'Month'; productrateplancharge.zqu__ZuoraId__c = MOCK_ZUORA_PROD_ID; productrateplancharge.zqu__ProductRatePlan__c = testproductrateplan.Id; productrateplancharge.zqu__Deleted__c = false; if (productrateplancharge.zqu__Model__c == 'Discount-Fixed Amount' || productrateplancharge.zqu__Model__c == 'Discount-Percentage') { productrateplancharge.zqu__Discount_Apply_Type__c = 3; productrateplancharge.zqu__Upto_How_Many_Periods__c = 5; productrateplancharge.zqu__Discount_Level__c = 'RatePlan'; } // Set List Price for non-tiered charge productrateplancharge.zqu__ListPrice__c = 1000; //creating Product rate plan charge with Model as 'Flat Fee Pricing' and Type as 'Recurring' zqu__ProductRatePlanCharge__c productrateplanchargeflatfee = new zqu__ProductRatePlanCharge__c(); productrateplanchargeflatfee.Name = 'Test Flat Fee Product Rate Plan Charge'; productrateplanchargeflatfee.zqu__Model__c = 'Flat Fee Pricing'; productrateplanchargeflatfee.zqu__Type__c = 'Usage'; productrateplanchargeflatfee.zqu__UOM__c = 'UOM tesing'; productrateplanchargeflatfee.zqu__DefaultQuantity__c = 1; productrateplanchargeflatfee.zqu__MinQuantity__c = 0; productrateplanchargeflatfee.zqu__MaxQuantity__c = 500; productrateplanchargeflatfee.zqu__RecurringPeriod__c = 'Month'; productrateplanchargeflatfee.zqu__ZuoraId__c = MOCK_ZUORA_PROD_ID; productrateplanchargeflatfee.zqu__ProductRatePlan__c = testproductrateplan.Id; productrateplanchargeflatfee.zqu__Deleted__c = false; productrateplanchargeflatfee.zqu__ListPrice__c = 1000; //creating Product rate plan charge with Model as 'Tiered Pricing' and Type as 'Recurring' zqu__ProductRatePlanCharge__c productrateplanchargetierpricing = new zqu__ProductRatePlanCharge__c(); productrateplanchargetierpricing.Name = 'Test Tiered Pricing Product Rate Plan Charge'; productrateplanchargetierpricing.zqu__Model__c = 'Tiered Pricing'; productrateplanchargetierpricing.zqu__Type__c = 'Usage'; productrateplanchargetierpricing.zqu__UOM__c = 'UOM tesing'; productrateplanchargetierpricing.zqu__DefaultQuantity__c = 1; productrateplanchargetierpricing.zqu__MinQuantity__c = 0; productrateplanchargetierpricing.zqu__MaxQuantity__c = 500; productrateplanchargetierpricing.zqu__RecurringPeriod__c = 'Month'; productrateplanchargetierpricing.zqu__ZuoraId__c = '1118e487366202430136757c960715b5'; productrateplanchargetierpricing.zqu__ProductRatePlan__c = testproductrateplan.Id; productrateplanchargetierpricing.zqu__Deleted__c = false; productrateplanchargetierpricing.zqu__ListPrice__c = 1000; List<zqu__ProductRatePlanCharge__c> productrateplanchargeList = new List<zqu__ProductRatePlanCharge__c>{productrateplanchargeflatfee,productrateplanchargetierpricing}; insert productrateplanchargeList; system.assertEquals([SELECT count() from zqu__ProductRatePlanCharge__c],2); //create quote amendment zqu__QuoteAmendment__c testQuoteAmendment = new zqu__QuoteAmendment__c(); testQuoteAmendment.zqu__Quote__c = quote.Id; insert testQuoteAmendment; system.assertEquals([SELECT count() from zqu__QuoteAmendment__c],1); //create quote rate plan zqu__QuoteRatePlan__c testQuoteRatePlan = new zqu__QuoteRatePlan__c(); testQuoteRatePlan.zqu__Quote__c = quote.Id; testQuoteRatePlan.zqu__ProductRatePlan__c = testproductrateplan.Id; testQuoteRatePlan.zqu__QuoteAmendment__c = testQuoteAmendment.Id; testQuoteRatePlan.zqu__QuoteProductName__c = 'Orange Box'; insert testQuoteRatePlan; system.assertEquals([SELECT count() from zqu__QuoteRatePlan__c],1); //create quote rate plan charge List<zqu__QuoteRatePlanCharge__c> testQuoteRatePlanChargeList = new List<zqu__QuoteRatePlanCharge__c>(); List<zqu__QuoteCharge_Tier__c> testquotechargetierList = new List<zqu__QuoteCharge_Tier__c>(); List<zqu__ProductRatePlanChargeTier__c> testproductrateplanchargetierList = new List<zqu__ProductRatePlanChargeTier__c>(); for(Integer productrateplanchargeIterator = 0;productrateplanchargeIterator < productrateplanchargeList.size();productrateplanchargeIterator++) { zqu__QuoteRatePlanCharge__c testQuoteRatePlanCharge = new zqu__QuoteRatePlanCharge__c(); testQuoteRatePlanCharge.zqu__QuoteRatePlan__c = testQuoteRatePlan.Id; testQuoteRatePlanCharge.zqu__ProductRatePlanCharge__c = productrateplanchargeList[productrateplanchargeIterator].Id; testQuoteRatePlanCharge.zqu__Model__c = productrateplanchargeList[productrateplanchargeIterator].zqu__Model__c; testQuoteRatePlanCharge.zqu__ChargeType__c = productrateplanchargeList[productrateplanchargeIterator].zqu__Type__c; testQuoteRatePlanChargeList.add(testQuoteRatePlanCharge); //create product rate plan charge tier zqu__ProductRatePlanChargeTier__c prodchargetier = new zqu__ProductRatePlanChargeTier__c( zqu__Active__c = false, zqu__Currency2__c = DEFAULT_CURRENCY, zqu__Currency__c = DEFAULT_CURRENCY, zqu__Deleted__c = false, zqu__EndingUnit__c = 0.0, zqu__IsOveragePrice__c = false, Name = '1', zqu__PriceFormat2__c ='Flat Fee', zqu__PriceFormat__c ='Flat Fee', zqu__Price__c = 1000, zqu__ProductRatePlanCharge__c = productrateplanchargeList[productrateplanchargeIterator].Id, zqu__StartingUnit__c = 0.0, zqu__Tier__c = 1 ); testproductrateplanchargetierList.add(prodchargetier); } insert testQuoteRatePlanChargeList; insert testproductrateplanchargetierList; system.assertEquals([SELECT count() from zqu__QuoteRatePlanCharge__c],2); system.assertEquals([SELECT count() from zqu__ProductRatePlanChargeTier__c],2); //create quote charge tier for(Integer quoterateplanchargeIterator = 0;quoterateplanchargeIterator < testQuoteRatePlanChargeList.size();quoterateplanchargeIterator++){ //create quote charge tier if(testQuoteRatePlanChargeList[quoterateplanchargeIterator].zqu__Model__c == 'Tiered Pricing') { zqu__QuoteCharge_Tier__c tier = new zqu__QuoteCharge_Tier__c( Name = String.valueOf('Tier ' + quoterateplanchargeIterator + 1), zqu__Tier__c = quoterateplanchargeIterator + 1, zqu__Price__c = quoterateplanchargeIterator + 1, zqu__Discount__c = 0, zqu__Effective_Price__c = 1000, zqu__StartingUnit__c = quoterateplanchargeIterator, zqu__EndingUnit__c = quoterateplanchargeIterator + 1, zqu__IsOveragePrice__c = false, zqu__QuoteRatePlanCharge__c = testQuoteRatePlanChargeList[quoterateplanchargeIterator].Id ); testquotechargetierList.add(tier); } } insert testquotechargetierList; system.assertEquals([SELECT count() from zqu__QuoteCharge_Tier__c],1); } @isTest private static void test_performCPQX() { zqu__Quote__c testquote = [SELECT Id from zqu__Quote__c LIMIT 1]; system.assertNotEquals(testquote,null); zqu__QuoteRatePlan__c testQuoteRatePlan = [SELECT Id, zqu__QuoteProductName__c, zqu__ProductRatePlan__c from zqu__QuoteRatePlan__c WHERE zqu__Quote__c =:testquote.Id]; system.assertNotEquals(testQuoteRatePlan,null); //Prepare the masterobject with parent object as zqu__Quote__c zqu.DataObject masterObject = new zqu.CommittedDataObject(testquote); system.assertNotEquals(masterObject,null); //Add the Quote Rate Plan Object as first child of the parent zqu__Quote__c zqu.DataObject rateplanObj = new zqu.CommittedDataObject(testQuoteRatePlan); system.assertNotEquals(rateplanObj,null); masterObject.addChild('zqu__QuoteRatePlan__c',rateplanObj); List<zqu.QPlan> qPlans= zqu.QPlanBuilder.makeFromCatalog(testquote.Id,new List<Id>{testQuoteRatePlan.zqu__ProductRatePlan__c}); for (zqu.QPlan qPlan : qPlans) { zqu.QPlanDataObject objList = new zqu.QPlanDataObject(qPlan); masterObject.addChild('zqu__QuoteRatePlan__c',objList); for(zqu.QCharge qCharge : qPlan.getCharges()) { zqu.QChargeDataObject obj = new zqu.QChargeDataObject(qCharge); masterObject.addChild('zqu__QuoteRatePlanCharge__c',obj); } } Map<String, Object> attributes = new Map<String, Object>(); List<String> logs = new List<String>(); Test.startTest(); MyCustomActionPlugin customPlugin = new MyCustomActionPlugin(); customPlugin.perform(masterObject, attributes, logs); customPlugin.isUpdateAction(); customPlugin.isAddRemoveAction(); customPlugin.isValidateAction(); Test.stopTest(); } @isTest private static void test_performCPQ9() { zqu__Quote__c testquote = [SELECT Id from zqu__Quote__c LIMIT 1]; system.assertNotEquals(testquote,null); zqu__QuoteRatePlan__c testQuoteRatePlan = [SELECT Id, zqu__QuoteProductName__c, zqu__ProductRatePlan__c from zqu__QuoteRatePlan__c WHERE zqu__Quote__c =:testquote.Id]; system.assertNotEquals(testQuoteRatePlan,null); //Prepare the masterobject with parent object as zqu__Quote__c zqu.DataObject masterObject = new zqu.CommittedDataObject(testquote); system.assertNotEquals(masterObject,null); //Add the Quote Rate Plan Object as first child of the parent zqu__Quote__c zqu.DataObject rateplanObj = new zqu.CommittedDataObject(testQuoteRatePlan); system.assertNotEquals(rateplanObj,null); masterObject.addChild('zqu__QuoteRatePlan__c',rateplanObj); //Get the Quote Rate Plan Charge Groups and add Quote Rate Plan Charge & Tier details as children List<zqu.zChargeGroup> chargeGroups = zqu.zQuoteUtil.getChargeGroups(testquote.Id, new List<Id>{testQuoteRatePlan.zqu__ProductRatePlan__c}); system.assertNotEquals(chargeGroups,null); for (zqu.zChargeGroup cg : chargeGroups) { zqu.DataObject dataCg = new zqu.ZChargeGroupDataObject(cg); for(zqu.DataObject chargedata : dataCg.getChildren('zqu__QuoteRatePlanCharge__c')) { masterObject.addChild('zqu__QuoteRatePlanCharge__c',chargedata); } } Map<String, Object> attributes = new Map<String, Object>(); List<String> logs = new List<String>(); Test.startTest(); MyCustomActionPlugin customPlugin = new MyCustomActionPlugin(); customPlugin.perform(masterObject, attributes, logs); customPlugin.isUpdateAction(); customPlugin.isAddRemoveAction(); customPlugin.isValidateAction(); Test.stopTest(); } }