1

I have a process that has been built that presents a visualforce page to the user, they first select a price book, and a section on page is refreshed. They are presented with a list of products from that price book. Once they select a product, a record is created on the Quoted_Products__c custom object. This process works fine and my problem is I have no idea how to write a unit test for simulating user selections on the visualforce page.

I have no test code to show as I have no idea where start. Doing some Google Fu I am only finding how to create test users and to run a test as a user and the like. I am not seeing anything on how to simulate a user making selections. Do I just make some assumptions on user selection and then run the very last process that creates the records? If so, how to set the required variables into the test page environment?

Below is my Controller and the Visualforce page. Any pointers would be great.

Visualforce Page

<apex:page Controller="queryProduct"> 

<apex:pageBlock >
    <apex:pageBlockSection title="Active Quote Detail">
        <apex:pageBlockTable value="{! quotedetails }" var="qd">
            <apex:column value="{! qd.Id }"/>
            <apex:column value="{! qd.Name }"/>
            <apex:column value="{! qd.Account__r.Name }"/>
            <apex:column value="{! qd.CurrencyIsocode }"/>
        </apex:pageBlockTable>
    </apex:pageBlockSection>

    <apex:pageBlockSection title="Pricebook selection">
        <apex:form >
            <apex:outputLabel value="Which Pricebook? :"/>
            <apex:selectList value="{! selected_Pricebook}" size="1">
                <apex:selectOptions value="{! pricebook_name}"/>
            </apex:selectList>
            <br /><br />
            <apex:commandButton value="Show Products" action="{!search}" rerender="out"/>
            <apex:commandButton value="Clear" action="{!clear}"/> 
        </apex:form>
    </apex:pageBlockSection>

    <apex:outputPanel id="out">
        <apex:pageBlock title="Selected PriceBook" rendered="{!If(selected_Pricebook !=null,true,false)}">
            <apex:pageBlockSection >
                <apex:outputText value="{!selected_Pricebook}"/>
            </apex:pageBlockSection>
        </apex:pageBlock>

        <apex:pageBlock title="Product Selection" rendered="{!If(selected_Pricebook !=null,true,false)}">
            <br />

            <apex:form > 
                Product Filter: 
                <apex:inputText id="filterValue" value="{!filterValue}" />
                <apex:commandButton value="Filter" action="{!search}" rerender="out"/>
                Enter characters to filter product selection.
                <br /><br /><br />
            </apex:form>

            <apex:form > 
                <apex:pageblocktable id="allprod" value="{!Products}" var="allprod">
                    <apex:column headervalue="Select Product">   
                        <apex:actionsupport action="{!useProduct}" event="onclick" rerender="out2">  
                            <input type="radio" />                    
                            <apex:param name="selected_Product" value="{!allprod.Product2Id}">
                            </apex:param>
                        </apex:actionsupport>
                    </apex:column>    
                    <apex:column headervalue="Product Code">
                        <apex:outputfield value="{!allprod.Name}">
                        </apex:outputfield></apex:column>
                    <apex:column headervalue="Product Code">
                        <apex:outputfield value="{!allprod.Product2.ProductCode}">
                        </apex:outputfield></apex:column>  
                    <apex:column headervalue="Product Description">
                        <apex:outputfield value="{!allprod.Product2.Description}">
                        </apex:outputfield></apex:column>
                </apex:pageblocktable>
            </apex:form>
        </apex:pageBlock>
        <apex:outputPanel rendered="{! ProdError }">
            <h1><div align="center">
                There are no products that match the currency of this quote in this PriceBook?
                </div></h1>
        </apex:outputPanel> 
    </apex:outputPanel>

    <apex:outputPanel id="out2" rendered="{!If(selected_Product !=null,true,false)}">
        <p>You have selected:</p>
        <apex:outputText value="{!selected_Product}"/>
    </apex:outputPanel>   

</apex:pageBlock>

Apex Controller

public class queryProduct {
//unit test ideas, need to add data into the fields via script and submit it seems
//https://developer.salesforce.com/forums/?id=906F0000000ALYKIA4


//Variables for working with the quote
public string quoteId;
public static List<Quote__c> myQuote;
//Variables for working with the pricebook
public String selected_Pricebook {get;set;}
public List<String> tmpString;// {get; set;}
public List<SelectOption> pricebook_name {get; set;}
public boolean ProdError { get; set; }
//Variables for working with the selected product
public string selected_Product {get;set;}
public string filterValue{get; set;}

public static List<Quote__c> getQuotedetails() {
    string quoteId =  ApexPages.currentPage().getparameters().get('Id');
    System.debug( 'Quote Id is ' + quoteId);
    myQuote = [SELECT Id, Name, Account__r.Name, Opportunity__r.Name, CurrencyIsoCode FROM Quote__c where Id =:quoteId];
    system.debug(string.valueOf(myQuote));
    return myQuote;
}
public queryProduct(){
    //Getting list of price books to show user. This list from their user profile assignment.
    pricebook_name = new List<SelectOption>();

    tmpString = new List<String>();
    User a = [SELECT PriceBooks__c
              FROM User
              WHERE Id =:UserInfo.getUserId()];
    tmpString.addAll(a.PriceBooks__c.split(';'));
    System.debug( 'Price books ' + tmpString);
    for( String b : tmpString ) {
        pricebook_name.add( new SelectOption( b, b ) ); 
    }
}

public List<PricebookEntry> getProducts(){
    //Getting the products in the selected price book
    //Need to turn the string into and ID first.
    System.debug( 'in getProducts, selected pricebook is: ' + selected_Pricebook);
    List<Pricebook2> pId = [Select Id from PriceBook2 where Name=:selected_Pricebook limit 1];
    List<PricebookEntry> productList;
    String baseQuery = 'Select Name, Product2Id, Product2.ProductCode, Product2.Description,UnitPrice from PriceBookEntry where Pricebook2Id=\''+pId[0].Id+'\' and CurrencyIsoCode =\''+myQuote[0].CurrencyIsoCode+'\'';
    if( !string.ISBLANK(filterValue) ) {
        string searchValue = '\'%'+ filterValue +'%\'';
        baseQuery = baseQuery + 'and Name like '+searchValue;
    }
    System.debug( 'in getProducts, product list query is: ' + baseQuery);
    productList = database.query(baseQuery);

    //Is there products to return? or a blank result set?
    if( productList.isEmpty() ) {
        ProdError = TRUE;
    } else {
        ProdError = FALSE;
    }
    System.debug('ProdError status: '+ProdError);
    System.debug('in getProducts prodlist var is: '+productList);
    return productList;
}
public PageReference search(){
    return null;
}
public PageReference useProduct(){
    //Need that price book ID again.
    List<Pricebook2> pId = [Select Id from PriceBook2 where Name=:selected_Pricebook limit 1];
    System.debug( 'in useProduct stage, Price book is seen as - ' + pId );

    String selected_Product = System.currentPagereference().getParameters().get('selected_Product');
    System.debug( 'in useProduct and the selected_Product ' + selected_Product);

    List<Product2> p = [select Description, ProductCode from Product2 WHERE Id=:selected_Product];
    System.debug( 'in useProduct stage, Product query is - ' + p );
    List<PricebookEntry> pb = [select UnitPrice from PricebookEntry WHERE Product2Id=:selected_Product and CurrencyIsoCode =:myQuote[0].CurrencyIsoCode];
    System.debug( 'in useProduct stage, PriceBookEntry is - ' + pb );
    //Create new quoted product record, now that all the data is collected
    QuotedProduct__c qp = new QuotedProduct__c(Quote__c = myQuote[0].Id,
                                               CurrencyIsoCode = myQuote[0].CurrencyIsoCode,
                                               Price_Book__c = pId[0].Id,
                                               Product__c = selected_Product,
                                               ListPrice__c = pb[0].UnitPrice,
                                               Starting_Price__c = pb[0].UnitPrice,
                                               Quantity__c = 1,
                                               ProductCode2__c = p[0].ProductCode,
                                               ProductDescription2__c = p[0].Description
                                              );
    try{
        insert qp;
    } catch (System.DmlException e){
        System.debug('ERROR: Creating the Quoted Product:' + e);
    }
    //Send user back to the new quoted Product record
    PageReference pageRef = new PageReference('/' + qp.Id);
    pageRef.setRedirect(true);
    return pageRef;
}
public void clear(){
    selected_Pricebook = null;
}}

Thanks,

1
  • The test class will be as good as any other test class that you may have written. In this case you will need to create test data to which will represent User input. – Jayant Das Jan 3 '19 at 19:26
6

There are several distinctions here that can be made that I think will help clarify things for you.

You aren't really testing the Visualforce page itself

Testing what the user sees, and the flow through a page/wizard/etc... is less like unit testing, and more like functional testing. Tools like Selenium are built for that type of work, while test classes in Apex really aren't well suited.

Honestly, I'd suggest that you forget (for the most part) that a Visualforce page is involved at all.

Inputs from Visualforce are just a fancy way to set a variable

When you see something like the following in your Visualforce

<apex:selectList value="{! selected_Pricebook}" size="1">
    <apex:selectOptions value="{! pricebook_name}"/>
</apex:selectList>

What you're doing is binding variables from your controller into the page (which allows Salesforce to render your page with dynamic data).

{!pricebook_name} here is an output (data flows from your controller, to the page), and {!selected_Pricebook} is an input (data flows from your page, into your controller).

Since Visualforce is just setting a value for the selected_Pricebook variable in your controller, you can "simulate" this by simply setting this variable programatically (i.e. through Apex). To Salesforce, there is very little/no difference between the two.

Focus on testing the logic in your controller

Your controller is just a class, much like any other.
Your controller has methods and variables, which are just like any other class.

Now, you'll probably need to step through a similar logic path that your users will end up taking (you need to make an instance of your controller, then set the pricebook name, then call getProducts(), etc...), but it really just is like testing any other apex class.

  • Set up your test data (the test environment)
  • Execute the one method that you want to test in this particular test method
  • Gather the data that results from you running the method under test, and make assertions to verify that the results are what you expect.

An example test method to help you on your way

@isTest
static void testGetQuoteDetails(){
    // Phase 1: test setup
    Pagereference testPage = Page.MyPageName;
    testPage.getParameters().put('id', '<some id here>');
    test.setCurrentPageReference(testPage);

    QueryProduct testController = new QueryProduct();

    // Phase 2: execution
    Test.startTest();
    List<Quote> results = testController.getQuoteDetails();
    Test.stopTest();

    // Phase 3: gather results and assert
    System.assertNotEquals(true, results.isEmpty(), 'getQuoteDetails() should have returned at least one quote');
}
3
  • This is helping greatly and I now have a test class that is working for validating the quote. I am now trying to understand why the following doesn't work. Pagereference testPage = Page.queryProduct_p; testPage.getParameters().put('id', q.Id); testPage.getParameters().put('selected_Pricebook', 'Perpetual'); QueryProduct testController = new QueryProduct(); test.setCurrentPageReference(testPage); Why does setting ID work, but not the selected_Pricebook? – PHonnold Jan 4 '19 at 21:26
  • @PHonnold You're starting to get into "this should be a new question" territory, but then again, I do suppose my example isn't the greatest. The answer is that the page parameters don't populate class variables. The page parameters are used in your getQuoteDetails() method on the line string quoteId = ApexPages.currentPage().getparameters().get('Id');. You're explicitly fetching a value from the page parameters. For things like selected_Pricebook, you'd get/set that like you would any other class variable: testController.selected_Pricebook = <value>; – Derek F Jan 4 '19 at 21:38
  • Thank you for the help. At this point I feel I have what I was looking for. – PHonnold Jan 4 '19 at 22:32
4

You can simulate user interaction by directly setting the properties to which your Visualforce components' values are bound, and by directly invoking the action methods that they fire. You can then validate the behavior or state of your controller class by writing assertions. (It's actually quite a bit simpler to test a straightforward Visualforce page controller than you might think).

For example, you can create a controller and directly set the value of your selected_Pricebook variable:

queryProduct qp = new queryProduct();

System.assertSomethingToShowThat(pricebook_name, isPopulatedCorrectlyForTestUser());

qp.queryProduct = qp.pricebook_name[0].getValue();

and then you can test further methods that are dependent on that user-populated variable, like getProducts().

You can call actions directly as methods:

PageReference pr = qp.search();
System.assertEquals(null, pr, 'should return null to reload page');

and subsequently validate any changes to the controller's state that those methods are expected to perform with further assertions.

One area where I tend to find Visualforce testing a little more challenging or tricky than other Apex classes is that there's often a linear path of expected user interaction - i.e., the user will do this, then that, then the third thing.

As a result, testing those interaction paths and the states through which the controller's expected to move can either produce long, complex unit tests that are fragile because they try to test too many things (more like an integration test) or more unit-like tests that involve a great deal of setup to reach the desired state and don't reflect the real use of the class's logic.

This page is simple enough that I don't think that divide is going to be a major issue for you. You've got methods that have pretty straightforward state changes and entry expectations:

  • getQuoteDetails() has no side effects and just depends on a URL parameter.
  • search() has no side effects but returns null.
  • clear() just sets one controller variable.
  • useProduct() performs DML and depends on selected_Pricebook and a URL parameter.

You should be able to write unit tests for these methods that create test data, set up the controller state as expected, perform the action, and validate the controller state afterwards without too much trouble.

The only major difference between testing these methods and testing any other Apex that works on data in similar ways is that you'll have to populate the parameters on the current Page Reference from your test code.

1
  • 1
    looks like (along w/ derek's answer) a canonical-qa :-) – cropredy Jan 3 '19 at 20:51

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.