function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
SIB AdminSIB Admin 

Trouble with Apex Test Class

I've just finished re-factoring some an old apex controller I wrote a while back. Field testing in the sandbox as far as functionality has shown it works great. Now I'm trying to write the test class for it and I'm having some serious issues. I'm unable to call any of the local variables from within the class. The test class stops running once it reaches line the creation of the aWrappers. It's like it doesn't query for any of the opportunities that are related to the audits. Any help would be greatly appreciated.

Apex Controller
/**
 * Author: Andrew Bettke
 * Date: 12/8/14
 * Last Revision: 10/21/2015
 *
 * The controller handles the 'SIBInvoiceWizard' custom visualforce page. This page will be used by the accounting department to mass create SIB Invoices
 * (along with billing event records) quickly and seamlessly from one page. The page will be accessed from a button on any active client account detail
 * page. From there, the page will dynamically displays editable data tables for each savings item that can billed for that month. After submitting the
 * form with, the controller will analyze the data and created invoice and billing event records accordingly. 
 **
 */
 
public class SIBInvoiceWizard {
	
	//The account for which we will be generating an invoice.
	public Account account {get; set;}
	
	//All audits processing savings for this account.
	
	public List<auditWrapper> aWrappers {get; set;}
	public SIB_Invoice__c invoice {get; set;}
	public Decimal invoiceTotal {get; set;}
	public Decimal inputTotal {get; set;}
	private static set<String> variableSavingsSources = new set<String> {'Generic VoIP Proposal','Plan Optimization',
																		 'Rate Reduction','Service Standardization',
																		 'Vendor Change','Vendor Consolidation'};
	
	//Upon creation, initialize variables and fill lists.
	public SIBInvoiceWizard(){
		
		
		//Instantiate the account by querying the database for the specified ID
		account = [SELECT ID, Name FROM Account Where Id = :ApexPages.currentPage().getParameters().get('accId')];
		
		//Instantiate the Invoice and link it to the Account.
		invoice = new SIB_Invoice__c();
		invoice.Account__c = account.Id;
		invoice.Amount_Paid__c = 0;
		invoiceTotal = 0.00;
		inputTotal = 0.00;
		
		//Grab a list of all relevant audits and their savings to fill the savings and audit wrappers.
		List<Location_Audits__c> auditsWithSavings = [SELECT Id, Name, Stage__c, Audit_Type__c,
									(SELECT ID, Name, Savings_Source__c, Type, Amount, Monthly_Contract_Value__c, CV__c, CV_Total_Billed__c, 
									        CV_Remaining__c, of_Months_Remaining__c, Recurrence_Type__c, Account.Contingency_Fee__c //Sub-query
									 FROM Opportunities__r
									 WHERE (StageName = 'Finalized Savings' OR StageName = 'Engagement Complete') AND CV_Remaining__c > 0)
							 FROM Location_Audits__c 
		                     WHERE Account__r.Id = :account.Id];
		 
		//Create a new list of auditWrappers and fill those auditWrappers with audits from the list above and a list of savingsWrappers.                    
		aWrappers = new List<auditWrapper>();
		for(Location_Audits__c audit : auditsWithSavings){
			List<savingsWrapper> sWrappers = new List<savingsWrapper>();
			for(Opportunity savingsItem : audit.Opportunities__r){
				Savings_Item_Billing_Event__c newSIBE = new Savings_Item_Billing_Event__c();
				newSIBE.Savings_Item__c = savingsItem.Id;
				newSIBE.CV_Billed__c = 0;
				sWrappers.add(new savingsWrapper(savingsItem,newSIBE));
			}
			if(sWrappers.size() > 0){
				aWrappers.add(new auditWrapper(audit,sWrappers));
			}
			
		}
		
	}
	
	public void calculateBillingAllocations(){
		for(auditWrapper a : aWrappers){
			Decimal adjustedCategoryBilling = a.categoryBillingTotal;
			Decimal includedVariableTotal = 0.00;
			//First Loop: Assign fixed billing amounts for one-time savings and fixed monthly savings.
			//While looping, sum the amounts for all included variable savings.
			for(savingsWrapper s : a.savingsWrappers){
				if(s.included == true){
					//We have a one-time or fixed montly savings item.
					if(!variableSavingsSources.contains(s.savingsItem.Savings_Source__c) ){
						s.allocationPercent = 100.00;
						s.sibe.CV_Billed__c = (s.savingsItem.Amount * (s.savingsItem.Account.Contingency_Fee__c/100)).setScale(2,RoundingMode.HALF_UP);
						adjustedCategoryBilling = adjustedCategoryBilling - s.sibe.CV_Billed__c;
					} else{
						includedVariableTotal = includedVariableTotal + s.savingsItem.Amount;
					}
				} else{
					s.allocationPercent = 0.00;
					s.sibe.CV_Billed__c = 0.00;
				}
			}
			
			//Second Loop: Assign allocated billing amounts for variable monthly savings.
			for(savingsWrapper s : a.savingsWrappers){
				if(s.included == true && includedVariableTotal > 0){
					//We have a variable monthly savings item
					if( variableSavingsSources.contains(s.savingsItem.Savings_Source__c) ){
						//Calculate the allocation % for the remaining amount of billing left to allocate.
						s.allocationPercent = ((s.savingsItem.Amount/includedVariableTotal) * 100).setScale(2,RoundingMode.HALF_UP);
						s.sibe.CV_Billed__c = (adjustedCategoryBilling * (s.allocationPercent/100)).setScale(2,RoundingMode.HALF_UP);
					}
				}
			}
		}
		
		invoiceTotal = 0.00;
		inputTotal = 0.00;
		for(auditWrapper a : aWrappers){
			inputTotal = inputTotal + a.categoryBillingTotal;
			for(savingsWrapper s : a.savingsWrappers){
				invoiceTotal = (invoiceTotal + s.sibe.CV_Billed__c).setScale(2,RoundingMode.HALF_UP);
			}
		}
		
		if(invoiceTotal != inputTotal){
			ApexPages.addMessage( 
								 new ApexPages.Message(ApexPages.Severity.ERROR,'The amount recieved from data entry does not match the calculated invoice total. Review your input and recalculate.')
								);
		} else{
			ApexPages.addMessage( 
								 new ApexPages.Message(ApexPages.Severity.CONFIRM,'The input total and calculated invoice total match! You may proceed with submission.')
								);
		}
	}
	
	
	//Called when the form is submitted.
	public PageReference formSubmission(){
		//Insert the created invoice into the database
		try{
			insert invoice;
		
			//Link each billingEvent created back to the newly inserted invoice, then insert the billing events.
			Integer lineItem = 1;
			for(auditWrapper a : aWrappers){
				for(savingsWrapper s : a.savingsWrappers){
					s.sibe.SIB_Invoice__c = invoice.Id;
					if(lineItem < 10){
						s.sibe.Name = invoice.Name + ' - 0' + lineItem;
					} else{
						s.sibe.Name = invoice.Name + ' - ' + lineItem;
					}
					
					if(s.sibe.CV_Billed__c != null || s.sibe.CV_Billed__c == 0){
						insert s.sibe;
						lineItem = lineItem + 1;
					}
				}
			}
			
			//Create a new page reference for the invoice and redirect the user to the newly created invoice.
			PageReference invoiceDetail = new PageReference('/'+invoice.Id);
			invoiceDetail.setRedirect(true);
			return invoiceDetail;
		} catch(DmlException e){
			if( e.getMessage().contains('DUPLICATE_VALUE') ){
				ApexPages.addMessage( 
								 new ApexPages.Message(ApexPages.Severity.ERROR,'Duplicate Invoice ID found. This Invoice # already exists.')
								);
				return ApexPages.currentPage();
			} else{
				ApexPages.addMessage( 
								 new ApexPages.Message(ApexPages.Severity.ERROR,'A generic DML exception has occured. Please contact the system administrator.')
								);
				return ApexPages.currentPage();
			}
			
		} catch(Exception e){
			ApexPages.addMessage( 
								 new ApexPages.Message(ApexPages.Severity.ERROR,'A generic exception error has occured. Please contact the system administrator.')
								);
			return ApexPages.currentPage();
		}
	}
	
	
	class auditWrapper{
		
		public Location_Audits__c audit {get; set;}
		public List<savingsWrapper> savingsWrappers {get; set;}
		public Decimal recurringVariableTotal {get; set;}
		public Decimal categoryTotal {get; set;}
		public Decimal categoryBillingTotal {get; set;}
		
		public auditWrapper(Location_Audits__c audit, List<savingsWrapper> savingsWrappers){
			this.audit = audit;
			this.savingsWrappers = savingsWrappers;
			this.categoryTotal = 0;
			this.categoryBillingTotal = 0.00;
		}
		
	}
	
	class savingsWrapper{
		
		public Opportunity savingsItem {get; set;}
		public Savings_Item_Billing_Event__c sibe {get; set;}
		public Decimal allocationPercent {get; set;}
		public Boolean included {get; set;}
		
		public savingsWrapper(Opportunity savingsItem, Savings_Item_Billing_Event__c sibe){
			this.savingsItem = savingsItem;
			this.sibe = sibe;
		}
		
	}

	
}

Visualforce Page
<apex:page controller="SIBInvoiceWizard" docType="html-5.0">
	
	<apex:includeScript value="/soap/ajax/29.0/connection.js"/>
	<apex:includeScript value="/soap/ajax/29.0/apex.js"/>
	    	
	<style type="text/css">
	
	    
		.col-md {
			width:10%;
		}
		.col-sm {
			width:5%;
		}
		
		.totals td{
	    	font-weight:bold;
	    }
	    
	    .totals .col1{
	    	width:77%;
	    }
	    
	
	</style>   
	    	
	
		<apex:pageMessages />
		<apex:sectionHeader title="SIB Invoice Wizard"/>
			
			<apex:form id="wizardForm">
			
				<apex:PageBlock id="invoiceDetails" title="Invoice Details">
					<table style="table-layout:fixed;width:50%;">
						<tr>
							<td>
								<apex:outputLabel value="Invoice # " style="font-weight:bold"/>
							</td>
							<td>
								<apex:outputLabel value="Invoice Date " style="font-weight:bold"/>
							</td>
							<td>
								<apex:outputLabel value="Amount Paid " style="font-weight:bold"/>
							</td>

						</tr>
						<tr>
							<td>
								<apex:inputField id="invoiceNumber" value="{!invoice.Name}"  required="true"/>
							</td>
							<td>
								<apex:inputField id="invoiceDate" value="{!invoice.Invoice_Date__c}"/>
							</td>
							<td>
								<apex:inputField id="amountPaid" value="{!invoice.Amount_Paid__c}"/>
							</td>
						</tr>
					</table>
					
					
				</apex:PageBlock>
				
				<apex:PageBlock id="savingsForm" onkeyup="totalBillingEvents();">
				
					
						<apex:repeat value="{!aWrappers}" var="a">
	
							<apex:pageBlockSection title="{!a.audit.Name + ' (' + a.audit.Audit_Type__c + ')'}" columns="1">
						
								<apex:outputPanel layout="block" style="width:100%;" id="savingsTables">
								
								<table>
									
									<tr>
										<th class="col-md">Name</th>
										<th class="col-md">Savings Source</th>
										<th class="col-sm">Type</th>
										<th class="col-sm">Recurrence Type</th>
										<th class="col-sm">Allocation Factor</th>
										<th class="col-sm">Monthly Savings</th>
										<th class="col-sm">Monthly CV</th>
										<th class="col-sm">CV Billed</th>
										<th class="col-sm">CV Remaining</th>
										<th class="col-sm">Current Billing</th>
										<th class="col-sm"># of Months Included</th>
										<th class="col-sm">Item Included</th>
									</tr>
									
									<apex:repeat value="{!a.savingsWrappers}" var="s">
									<tr>
										<td class="col-md"><apex:outputLink value="/{!s.savingsItem.Id}">{!s.savingsItem.Name}</apex:outputLink></td>
										<td class="col-md">{!s.savingsItem.Savings_Source__c}</td>
										<td class="col-sm">{!s.savingsItem.Type}</td>
										<td class="col-sm">{!s.savingsItem.Recurrence_Type__c}</td>
										<td class="col-sm">{!s.allocationPercent}%</td>
										<td class="col-sm">${!s.savingsItem.Amount}</td>
										<td class="col-sm">${!s.savingsItem.Monthly_Contract_Value__c}</td>
										<td class="col-sm">${!s.savingsItem.CV_Total_Billed__c}</td>
										<td class="col-sm">${!s.savingsItem.CV_Remaining__c}</td>
										<td class="col-sm">
											<apex:outputText value="{0, number, currency}" style="width:70%;">
												<apex:param value="{!s.sibe.CV_Billed__c}"/>
											</apex:outputText>
										</td>
										<td class="col-sm"><apex:inputField value="{!s.sibe.of_Months_Included__c}" style="width:25%;"/></td>
										<td class="col-sm"><apex:inputCheckbox value="{!s.included}" style="text-align:center;"/></td>
									</tr>
									</apex:repeat>
									
									
									<tr class="totals">
										<td  colspan="9">Total Billed for Audit Category</td>
										<td><apex:inputText value="{!a.categoryBillingTotal}" style="width:70%;"/></td>
									</tr>
									
								</table>
										
								</apex:outputPanel>
	
							</apex:pageBlockSection>
							
						</apex:repeat>
						
					<apex:pageBlockSection columns="1">
					<apex:outputPanel layout="block" style="width:100%;">
						<table class="totals" style="width:100%;margin-top:2%">
							<tr>
								<td class="col1">Invoice Total</td>
								<td><apex:outputText value="{0, number, currency}" style="color:{!IF(invoiceTotal==inputTotal,'green','red')};">
										<apex:param value="{!invoiceTotal}"/>
									</apex:outputText>
								</td>
							</tr>
							<tr class="totals">
								<td class="col1">Billing Input Total</td>
								<td><apex:outputText value="{0, number, currency}">
										<apex:param value="{!inputTotal}"/>
									</apex:outputText>
								</td>
							</tr>
							<tr>
								<td class="col1"></td>
								<td><apex:commandButton value="Calculate" rerender="" action="{!calculateBillingAllocations}"/></td>
							</tr>
							<tr>
								<td class="col1"></td>
								<td><apex:commandButton value="Submit" rerender="" action="{!formSubmission}" disabled="{!(invoiceTotal=0 ||
																														   inputTotal=0 ||
																														   invoiceTotal != inputTotal)
																														}"/>
								</td>
							</tr>
						</table>
					</apex:outputPanel>
					</apex:pageBlockSection>
					
				</apex:PageBlock>
				
			</apex:form>
			
			<script type="text/javascript">
		
				function totalBillingEvents(){
					var itemAmountElements = document.getElementById("{!$Component.wizardForm.savingsForm}").getElementsByTagName("input");
					var total = 0;
		
					for(i = 0; i < itemAmountElements.length; i++){
						var currency = itemAmountElements[i].value;
						var number = Number(currency.replace(/[^0-9\.-]+/g,""));
						total = total + number;
						i = i + 1
					}
					
					document.getElementById("{!$Component.wizardForm.savingsForm.invoiceTotal}").innerHTML = "$"+total.toFixed(2);
				}		
		
			</script>
	
	</apex:page>
Apex Test Class
/**
 *
 */
@isTest
private class SIBInvoiceWizardTest {

    static testMethod void SIBInvoiceWizardMainTest() {
        Account testAccount = new Account(Name = 'Test Account', RecordTypeId = '012C0000000GCJv', Industry = 'Banking',Status__c = 'Active');
        insert testAccount;
        
        Location_Audits__c testAudit = new Location_Audits__c(Account__c = testAccount.Id, Stage__c = 'Audit Complete - Processing Savings', Audit_Type__c = 'CO2', Current_Analyst__c = 'Andrew Bettke');
        insert testAudit;
        
        Opportunity testSavingsItem = new Opportunity(Name = 'testSavings', Account = testAccount, Audit_Number__c = testAudit.Id, of_months__c = 1, StageName = 'Not Yet Proposed', CloseDate = system.today(), Implementer__c = 'Andrew Bettke', Double_Checker__c = 'Andrew Bettke', Expected_Completion_Initial_Validation__c = system.today());
        insert testSavingsItem;
        
        PageReference testPage = new PageReference('/apex/SIBInvoiceWizard?accId='+testAccount.Id);
        Test.setCurrentPage(testPage);
        
        
        Test.startTest();
        
        SIBInvoiceWizard wizardController = new SIBInvoiceWizard();
        
        wizardController.invoice.Name = '123456';
        wizardController.invoice.Invoice_Date__c = system.today();
        //wizardController.aWrappers[0].savingsWrappers[0].sibe.CV_Billed__c = 100;
        //wizardController.aWrappers[0].savingsWrappers[0].sibe.of_Months_Included__c = 1;
        //wizardController.aWrappers[0].savingsWrappers[0].included = true;
        //wizardController.calculateBillingAllocations();
        wizardController.formSubmission();
        
        Test.stopTest();
        
    }
}


 
pconpcon
Sorry this is going to take a little bit, but there is a lot of code going on and it's not very clear to me what is and is not happening.  When you say "The test class stops running once it reaches line the creation of the aWrappers," how are you asserting this?  Since I do not see any System.asserts in your test, I'm just trying to figure out how you know it's not doing the right thing.
SIB AdminSIB Admin
Well, as the test class is right now, when I run the test to view the code coverage for the whole class, the coverage shows that begining within the loop at line 56 the coverage stops, meaning the test never makes it to that line. The only reason that should happen is if the list list of opportunites that is retrieved from the subquery done at line 44 has returned 0 results and so no savingsWrappers and no auditWrappers ever get created.

I tried to verify if this was the case, and whenever I tried to System.Assert(auditsWithSavings.size() > 0) the code would not save saying that that variable did not exist. I'm not sure if the test records I'm creating aren't actually being inserted or if the query itself is not looking for the correct records to pull. Does that help at all?
pconpcon
I think your issue may be with your how you set the Id on the PageReference.  I also took the liberty of updating your constructor a little bit to make things a little clearer and to increase performance.  In your query you'll get a better speed result if you use Account__c instead of Account__r.Id since it won't have to do the dereference before doing the query.  (If you're not using the Account.Name anywhere then you could just use line 5 and store it on an Id variable and save yourself a SOQL query)
 
public SIBInvoiceWizard() {
    this.account = [
        select Name
        from Account
        where Id = :ApexPages.currentPage().getParameters().get('accId')
    ];
    
    this.invoice = new SIB_Invoice__c(
        Account__c = account.Id,
        Amount_Paid__c = 0
    );

    this.invoiceTotal = 0.00;
    this.inputTotal = 0.00;
    
    List<Location_Audits__c> auditsWithSavings = [
        select Name,
            Stage__c,
            Audit_Type__c,
            (
                select Name,
                    Savings_Source__c,
                    Type,
                    Amount,
                    Monthly_Contract_Value__c,
                    CV__c,
                    CV_Total_Billed__c, 
                    CV_Remaining__c,
                    of_Months_Remaining__c,
                    Recurrence_Type__c,
                    Account.Contingency_Fee__c //Sub-query
                from Opportunities__r
                where (
                    StageName = 'Finalized Savings' or
                    StageName = 'Engagement Complete'
                ) and
                CV_Remaining__c > 0
        )
        from Location_Audits__c 
        where Account__r.Id = :account.Id
    ];
        
    this.aWrappers = new List<auditWrapper>();

    for (Location_Audits__c audit : auditsWithSavings) {
        List<savingsWrapper> sWrappers = new List<savingsWrapper>();
        for (Opportunity savingsItem : audit.Opportunities__r) {
            sWrappers.add(
                new SavingsWrapper(
                    new Savings_Item_Billing_Event__c(
                        Savings_Item__c = savingsItem.Id,
                        CV_Billed__c = 0
                    )
                )
            );
        }

        if (!sWrappers.isEmpty()) {
            aWrappers.add(new auditWrapper(audit, sWrappers));
        }
    }
}

And then if you update your test to this it should work.  On line 33 I'm using the Page directly instead of relying on the parsing of the url.  This will make things a little more tightly bound so that you don't accidentally break your tests later down the line by removing a page the test is using.  And then by adding the parameters to the map (line 34), we everything should be populated correctly.
 
@isTest
private class SIBInvoiceWizardTest {
    static testMethod void SIBInvoiceWizardMainTest() {
        Account testAccount = new Account(
            Name = 'Test Account',
            RecordTypeId = '012C0000000GCJv',
            Industry = 'Banking',
            Status__c = 'Active'
        );
        insert testAccount;
        
        Location_Audits__c testAudit = new Location_Audits__c(
            Account__c = testAccount.Id,
            Stage__c = 'Audit Complete - Processing Savings',
            Audit_Type__c = 'CO2',
            Current_Analyst__c = 'Andrew Bettke'
        );
        insert testAudit;
        
        Opportunity testSavingsItem = new Opportunity(
            Name = 'testSavings',
            Account = testAccount,
            Audit_Number__c = testAudit.Id,
            of_months__c = 1,
            StageName = 'Not Yet Proposed',
            CloseDate = System.today(),
            Implementer__c = 'Andrew Bettke',
            Double_Checker__c = 'Andrew Bettke',
            Expected_Completion_Initial_Validation__c = System.today()
        );
        insert testSavingsItem;

        PageReference testPage = Page.SIBInvoiceWizard;
        testPage.getParameters().put('accId', testAccount.Id);
        Test.setCurrentPage(testPage);
        
        Test.startTest();
        
        SIBInvoiceWizard wizardController = new SIBInvoiceWizard();
        
        wizardController.invoice.Name = '123456';
        wizardController.invoice.Invoice_Date__c = System.today();
        //wizardController.aWrappers[0].savingsWrappers[0].sibe.CV_Billed__c = 100;
        //wizardController.aWrappers[0].savingsWrappers[0].sibe.of_Months_Included__c = 1;
        //wizardController.aWrappers[0].savingsWrappers[0].included = true;
        //wizardController.calculateBillingAllocations();
        wizardController.formSubmission();
        
        Test.stopTest();
    }
}

NOTE: This code has not been tested and may contain typographical / logical errors.
SIB AdminSIB Admin
Thanks for the optimization.  I gave those changes a shot but still no go. When checking which lines the test class is actually reaching I'm not seeing it hightlight between lines 20 and 37 (the opportunity subquery). Does that mean that that subquery isn't even taking place? If that is so, then what will happen every time is that no opportunities will be queried and no auditWrappers created. Do test classes not support subqueries like this? Will I need to break these into 2 queries?