I'm just starting out with Apex, really any SF development. I've been lucky enough to be able to learn and practice what I've been learning in real-life scenarios, but I've depleted my googling skills I believe.

I have an Apex Class, Apex Test Class, VisualForce Component, and a VisualForce Email Template. I am struggling to build/understand the Apex Test class to reach the 75% coverage I need. I believe I have to create test data to run the test class on and I think that's where I'm losing it. Besides the test class error, the process I've made is the visualforce email template pulls the quote Id from the quote it's launched from which passes it to the apex class where the SOQL query pulls and filters the quote lines and finally passing to the visualforce component reorganizes the data for the visualforce email template. That is in my very layman's terms. Any help would be awesome.


  • I've disabled the CPQ triggers at the advice of cropredy and it's allowed the test to run to completion and pass, but the code coverage for my apex class is still 0%.

Apex Class

  public class Exclude_Optional_Group_Lines_Template {
    public Id sbaaquoteId {get;set;}
    public List<SBQQ__QuoteLine__c> getquotelines()
        List<SBQQ__QuoteLine__c> quotelines;
        quotelines = [SELECT SBQQ__Number__c ,SBQQ__ProductName__c, SBQQ__Quantity__c, SBQQ__ListTotal__c, 
                             SBQQ__AdditionalDiscount__c, Additional_Usage_Discount__c,
                             Annual_Payment_Processing_Amount__c, SBQQ__NetTotal__c
                             FROM SBQQ__QuoteLine__c WHERE SBQQ__Quote__c =: sbaaquoteId AND SBQQ__Optional__c = FALSE];
        return quotelines;

enter image description here

Apex Test Class

Public class Exclude_Optional_Lines_Test_Class{
    static testMethod void getquotelines(){
        Exclude_Optional_Group_Lines_Template eq = new Exclude_Optional_Group_Lines_Template();
        Account a = new Account();
            a.Name = 'Unit_test';
        insert a;
        date mydate = date.parse('05/01/2050');
        Opportunity o = new Opportunity();
            o.AccountId = a.Id;
            o.RecordTypeId = '0120L000000fOnWQAU';
            o.Name = 'Unit_test';
            o.StageName = 'Diagnostic';
            o.CloseDate = mydate;
            o.Pricebook2Id = '01s8L000000GsccQAC';
        insert o;
        SBQQ__Quote__c q = new SBQQ__Quote__c();
            q.SBQQ__Opportunity2__c = o.Id;
            q.Autorenew__c = TRUE;
            q.Billing_Method__c = 'Annual Annual';
            q.SBQQ__SubscriptionTerm__c = 12;
            q.SBQQ__StartDate__c = mydate;
            q.SBQQ__Primary__c = TRUE;
        insert q;
        SBQQ__QuoteLine__c cpq = new SBQQ__QuoteLine__c();
        cpq.SBQQ__Quote__c = q.Id;
        cpq.SBQQ__Product__c = '01t4W00000D6qJeQAJ';
        cpq.SBQQ__Number__c = 1;
        cpq.SBQQ__Quantity__c = 1;
        cpq.SBQQ__Optional__c = FALSE;
        insert cpq;
        Id sbaaquoteId = q.Id;
        SBQQ__QuoteLine__c cpq2 = [SELECT Id, SBQQ__ProductName__c, SBQQ__Number__c, SBQQ__Quantity__c, SBQQ__Optional__c
                                   FROM SBQQ__QuoteLine__c 
                                   WHERE SBQQ__Quote__c =: sbaaquoteId AND SBQQ__Optional__c = FALSE
                                   LIMIT 1];
        System.assert(cpq2 != NULL);

VisualForce Component

<apex:component controller="Exclude_Optional_Group_Lines_Template" access="global">
    <apex:attribute name="QuoteId" type="Id" description="Id of the CPQ quote" assignTo="{!sbaaquoteId}"/>
    <table border = ".05" cellspacing = "1">
            <td>Product Name</td>
            <td>List Total</td>
            <td>Additional Discount</td>
            <td>Additional Usage Discount</td>
            <td>Annual Payment Processing Amount</td>
            <td>Net Total</td>
        <apex:repeat value="{!quotelines}" var="o">
            <td>{!o.Additional_Usage_Discount__c} %</td>
            <td>{!o.SBQQ__NetTotal__c }</td>

VisualForce Email Template

<messaging:emailTemplate subject="Quote Approval - REQUESTED: {!relatedTo.AQS_Quote__r.Name} for {!relatedTo.AQS_Quote__r.SBQQ__Opportunity2__r.Name}"
<messaging:HtmlEmailBody >

<apex:image value="{!LEFT($Api.Enterprise_Server_URL_300, FIND('/services',$Api.Enterprise_Server_URL_300))}/servlet/servlet.ImageServer?id={!$Setup.AQS_AA_Enablement_IDs__c.AQS_Approval_Email_Logo_ID__c}&oid={!$Organization.Id}" 


Hello {!recipient.name},

<p>{!relatedTo.AQS_Quote__r.Owner.Name} is requesting your approval for a Quote, please see below.<br/></p>

<p><b>Quote Name: </b>&nbsp;<apex:outputLink value="{!LEFT($Api.Enterprise_Server_URL_300, FIND('/services',$Api.Enterprise_Server_URL_300))}/{!relatedTo.AQS_Quote__r.Id}" >{!relatedTo.AQS_Quote__r.Name}</apex:outputLink><br/>

<b>Opportunity Name: </b>&nbsp;<apex:outputLink value="{!LEFT($Api.Enterprise_Server_URL_300, FIND('/services',$Api.Enterprise_Server_URL_300))}/{!relatedTo.AQS_Quote__r.SBQQ__Opportunity2__r.Id}" >{!relatedTo.AQS_Quote__r.SBQQ__Opportunity2__r.Name}</apex:outputLink><br/>

<b>Opportunity Owner: </b>{!relatedTo.AQS_Quote__r.SBQQ__Opportunity2__r.Owner.Name}<br/>

<b>Notes for Approval: </b>{!relatedTo.AQS_Quote__r.Notes_for_Approval__c}<br/>

<b>Start Date: </b><apex:outputText value="{0, date, MMMM d','  yyyy}">
    <apex:param value="{!relatedTo.AQS_Quote__r.SBQQ__StartDate__c}" /> 

<b>Subscription Term: </b><apex:outputText value="{0, number, 00}">
    <apex:param value="{!relatedTo.AQS_Quote__r.Calculated_Subscription_Term__c}" />

<b>End Date: </b><apex:outputText value="{0, date, MMMM d','  yyyy}">
    <apex:param value="{!relatedTo.AQS_Quote__r.Calculated_End_Date__c}" /> 

<b>Opt Out End Date: </b><apex:outputText value="{0, date, MMMM d','  yyyy}">
    <apex:param value="{!relatedTo.AQS_Quote__r.Opt_Out_End_Date_Formula__c}" /> 

<b>Currency Code: </b><apex:outputText value="{0}">
    <apex:param value="{!relatedTo.AQS_Quote__r.CurrencyIsoCode}" />

<p><b>Next Step:</b> To approve or reject this request, reply to this email with the word APPROVE, APPROVED, YES, REJECT, REJECTED, or NO in the first line of the email message, or click on one of these links:&nbsp;&nbsp;<apex:outputLink value="{!LEFT($Api.Partner_Server_URL_140,FIND('.com',$Api.Partner_Server_URL_140)+4)+'apex/SBAA__Approve?id='+relatedTo.Id}">Approve</apex:outputLink>&nbsp;&nbsp;&nbsp;<apex:outputLink value="{!LEFT($Api.Partner_Server_URL_140,FIND('.com',$Api.Partner_Server_URL_140)+4)+'apex/SBAA__Reject?id='+relatedTo.Id}">Reject</apex:outputLink><br/></p>

<sbaa:relatedList rendered="{!relatedTo.AQS_Quote__c != null}" recordId="{!relatedTo.AQS_Quote__c}" relationshipName="AQS_AA_Approvals__r" fieldSetName="AQS_Approval_Request"/>

<b>Quote Line Items:</b><br/>
<c:Approval_Quote_Lines_List QuoteId="{!relatedTo.AQS_Quote__r.Id}"/><br/><br/>

<p style="color:white"><apex:outputText value="{!relatedTo.Id}"/></p>
  • your error has to do with mocking CPQ quotelines - it has nothing to do with the VF component; this Help article may be of assistance (note the disabling of CPQ triggers)
    – cropredy
    Jun 3, 2022 at 0:30
  • I've disabled the CPQ triggers and now it's completing the test but it's 0% for the class I need coverage for.
    – KB2021
    Jun 3, 2022 at 1:39

1 Answer 1


To test a VF controller, you need to construct it as an object and then provide the parameters to the object as if they were provided by your VF Email markup

So, you have ...

Id sbaaquoteId = q.Id;
SBQQ__QuoteLine__c cpq2 = [SELECT Id, SBQQ__ProductName__c, SBQQ__Number__c, SBQQ__Quantity__c, SBQQ__Optional__c
                               FROM SBQQ__QuoteLine__c 
                               WHERE SBQQ__Quote__c =: sbaaquoteId AND SBQQ__Optional__c = FALSE
                               LIMIT 1];

You need:

// given a component controller w/ assigned quoteId
Exclude_Optional_Group_Lines_Template ctlr = new Exclude_Optional_Group_Lines_Template();
ctlr.sbaaquoteId = q.Id; // the mocked Quote

// when the component requests the line items
List<SBQQ__QuoteLine__c> quoteLines = ctlr.getquotelines();

// then verify the quoteLines agree with mocked value(s)
system.assertEquals(...exercise left for you...)
  • I was able to get 100% coverage using System.assert(quotelines != NULL);
    – KB2021
    Jun 3, 2022 at 13:59
  • 1
    Coverage is not the goal, assertion of correct behavior is the goal. Coverage is a side effect of proper assertions and test method construction
    – cropredy
    Jun 3, 2022 at 14:52

You must log in to answer this question.

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