0

I have a test class for a custom Visualforce page controller and I am getting the following error message:

Error Message System.NullPointerException: Attempt to de-reference a null object Stack Trace Class.VF_opportunityProductEntryExtension.updateAvailableList: line 91, column 1
Class.VF_opportunityProductEntryExtension.: line 53, column 1
Class.VF_opportunityProductEntryTests.theTests: line 40, column 1

Does anyone know why I am getting this error? (Sorry for the long code)

Test Class

@istest
private class VF_opportunityProductEntryTests {

    static testMethod void theTests(){

    Account acc = new Account(Name = 'Test');
    insert acc;

    Contact con = new Contact(FirstName = 'Test',LastName = 'Name',AccountId = acc.Id,);
    insert con;

    Opportunity opp = new Opportunity(AccountId = acc.Id,Contact__c = con.Id,Name = 'Test Account - Opp',CloseDate = date.today(),StageName = 'Order Pending',Type = 'New',CurrencyIsoCode = 'USD');
    insert opp;

    Product2 newProd = new Product2(Name = 'test product',ProductCode = 'aaaaa',isActive = true);
    insert newProd;

    Id pricebookId = Test.getStandardPricebookId();

    Opportunity opp2 = [SELECT Pricebook2.Id FROM Opportunity WHERE Id =: opp.Id];

    PriceBookEntry pbStd = new PriceBookEntry(UnitPrice = 300.00,CurrencyIsoCode = 'USD',PriceBook2Id = pricebookId,Product2Id = newProd.Id,IsActive = true);
    insert pbStd;

    PriceBookEntry pbEntry2 =  new PriceBookEntry(PriceBook2Id = Opp2.Pricebook2Id,Product2Id = newProd.Id,UnitPrice = 300.00,IsActive = true);
    insert pbEntry2;

    OpportunityLineItem ol = new OpportunityLineItem(pricebookentryid=pbEntry2.Id,Quantity = 1,UnitPrice = 1000.00,OpportunityID = opp.Id);
    insert ol;

        OpportunityLineItem oli = [SELECT Id, Opportunity.Id, PricebookEntryId, PricebookEntry.Pricebook2Id, PricebookEntry.Name, PriceBookEntry.Product2Id, OpportunityId, Opportunity.AccountId 
                                    FROM OpportunityLineItem limit 1
                                    WHERE Id =: ol.Id];

        // load the page       
        PageReference pageRef = Page.VF_opportunityProductEntry;
        pageRef.getParameters().put('Id',oli.OpportunityId);
        Test.setCurrentPageReference(pageRef);

        // load the extension
        //Line 40 ERROR
        VF_opportunityProductEntryExtension oPEE = new VF_opportunityProductEntryExtension(new ApexPages.StandardController(ol.Opportunity));

        // test 'getChosenCurrency' method
        if(UserInfo.isMultiCurrencyOrganization())
            System.assert(oPEE.getChosenCurrency()!='');
        else
            System.assertEquals(oPEE.getChosenCurrency(),'');

        // we know that there is at least one line item, so we confirm
        Integer startCount = oPEE.ShoppingCart.size();
        system.assert(startCount>0);

        //test search functionality without finding anything
        oPEE.searchString = 'TESTING STRING';
        oPEE.updateAvailableList();
        system.assert(oPEE.AvailableProducts.size()==0);

        //test remove from shopping cart
        oPEE.toUnselect = ol.PricebookEntryId;
        oPEE.removeFromShoppingCart();
        system.assert(oPEE.shoppingCart.size()==startCount-1);

        //test save and reload extension
        oPEE.onSave();
        oPEE = new VF_opportunityProductEntryExtension(new ApexPages.StandardController(ol.Opportunity));
        system.assert(oPEE.shoppingCart.size()==startCount-1);

        // test search again, this time we will find something
        oPEE.searchString = ol.PricebookEntry.Name;
        oPEE.updateAvailableList();
        system.assert(oPEE.AvailableProducts.size()>0);       

        // test add to Shopping Cart function
        oPEE.toSelect = oPEE.AvailableProducts[0].Id;
        oPEE.addToShoppingCart();
        system.assert(oPEE.shoppingCart.size()==startCount);

        // test save method - WITHOUT quanitities and amounts entered and confirm that error message is displayed
        oPEE.onSave();
        system.assert(ApexPages.getMessages().size()>0);

        // add required info and try save again
        for(OpportunityLineItem o : oPEE.ShoppingCart){
            o.quantity = 5;
            o.unitprice = 300;
        }
        oPEE.onSave();

        // query line items to confirm that the save worked
        opportunityLineItem[] ol2 = [SELECT Id from opportunityLineItem 
                                        WHERE OpportunityId = :ol.OpportunityId];
        system.assert(ol2.size()==startCount);

        // test on new Opp (no pricebook selected) to make sure redirect is happening
        Opportunity newOpp = new Opportunity(Name='New Opp',stageName='Pipeline',Amount=10,closeDate=System.Today()+30,AccountId=ol.Opportunity.AccountId);
        insert(newOpp);
        oPEE = new VF_opportunityProductEntryExtension(new ApexPages.StandardController(newOpp));
        System.assert(oPEE.priceBookCheck()!=null);

        // final quick check of cancel button
        System.assert(oPEE.onCancel()!=null);

        // load the page
        pageRef = Page.VF_opportunityProductRedirect;
        pageRef.getParameters().put('Id',ol2[0].Id);
        Test.setCurrentPageReference(pageRef);

        // load the extension and confirm that redirect function returns something
        VF_opportunityProductRedirectExtension oPRE = new VF_opportunityProductRedirectExtension(new ApexPages.StandardController(ol2[0]));
        System.assert(oPRE.redirect()!=null);

    System.debug(LoggingLevel.ERROR, '#####ERROR');
    }
}

Controller

    public with sharing class VF_opportunityProductEntryExtension {

    public Opportunity theOpp {get;set;}
    public String searchString {get;set;}
    public opportunityLineItem[] shoppingCart {get;set;}
    public priceBookEntry[] AvailableProducts {get;set;}
    public Pricebook2 theBook {get;set;}   

    public String toSelect {get; set;}
    public String toUnselect {get; set;}
    public Decimal Total {get;set;}

    public Boolean portfolio {get;set;}
    public Boolean portfolioChk {get;set;}
    public Boolean overLimit {get;set;}
    public Boolean multipleCurrencies {get; set;}

    private Boolean forcePricebookSelection = false;

    private opportunityLineItem[] forDeletion = new opportunityLineItem[]{};


    public VF_opportunityProductEntryExtension(ApexPages.StandardController controller) {

        // Need to know if org has multiple currencies enabled
        multipleCurrencies = UserInfo.isMultiCurrencyOrganization();

        // Get information about the Opportunity being worked on
        if(multipleCurrencies)
            theOpp = database.query('select Id, Pricebook2Id, Pricebook2.Name, CurrencyIsoCode, Contributor_Login__r.Account__r.Tier__c from Opportunity where Id = \'' + controller.getRecord().Id + '\' limit 1');
        else
            theOpp = [select Id, Pricebook2Id, PriceBook2.Name, Contributor_Login__r.Account__r.Tier__c from Opportunity where Id = :controller.getRecord().Id limit 1];

        // If products were previously selected need to put them in the "selected products" section to start with
        shoppingCart = [SELECT Id, ENT_Portfolio__c, ENT_Portfolio_Rate__c, Quantity, TotalPrice, UnitPrice, Description, PriceBookEntryId, PriceBookEntry.Name, PriceBookEntry.IsActive, PriceBookEntry.Product2Id, PriceBookEntry.Product2.Description, PriceBookEntry.Product2.Name, PriceBookEntry.Product2.ProductCode, PriceBookEntry.PriceBook2Id FROM opportunityLineItem WHERE OpportunityId=:theOpp.Id];

        // Check if Opp has a pricebook associated yet
        if(theOpp.Pricebook2Id == null){
            Pricebook2[] activepbs = [select Id, Name from Pricebook2 where isActive = true limit 2];
            if(activepbs.size() == 2){
                forcePricebookSelection = true;
                theBook = new Pricebook2();
            }
            else{
                theBook = activepbs[0];
            }
        }
        else{
            theBook = theOpp.Pricebook2;
        }

        if(!forcePricebookSelection)
        //Line 53 ERROR
            updateAvailableList();
    }

    // this is the 'action' method on the page
    public PageReference priceBookCheck(){

        // if the user needs to select a pricebook before we proceed we send them to standard pricebook selection screen
        if(forcePricebookSelection){        
            return changePricebook();
        }
        else{

            //if there is only one active pricebook we go with it and save the opp
            if(theOpp.pricebook2Id != theBook.Id){
                try{
                    theOpp.Pricebook2Id = theBook.Id;
                    update(theOpp);
                }
                catch(Exception e){
                    ApexPages.addMessages(e);
                }
            }

            return null;
        }
    }

    public String getChosenCurrency(){

        if(multipleCurrencies)
            return (String)theOpp.get('CurrencyIsoCode');
        else
            return '';
    }

    public void updateAvailableList() {

        // We dynamically build a query string and exclude items already in the shopping cart
        //Line 91 ERROR
        String qString = 'select Id, Pricebook2Id, IsActive, Product2.Name, Product2.Description, Product2.ProductCode, Product2.Family, Product2.IsActive, UnitPrice from PricebookEntry where IsActive=true and Pricebook2Id = \'' + theBook.Id + '\'';
        if(multipleCurrencies)
            qstring += ' and CurrencyIsoCode = \'' + theOpp.get('currencyIsoCode') + '\'';

        // note that we are looking for the search string entered by the user in the name OR description
        // modify this to search other fields if desired
        if(searchString!=null){
            qString+= ' and (Product2.Name like \'%' + searchString + '%\' or Product2.Description like \'%' + searchString + '%\')';
        }

        Set<Id> selectedEntries = new Set<Id>();
        for(opportunityLineItem d:shoppingCart){

            selectedEntries.add(d.PricebookEntryId);
        }

        if(selectedEntries.size()>0){
            String tempFilter = ' and Id not in (';
            for(Id i : selectedEntries){
                tempFilter+= '\'' + (String)i + '\',';
            }
            String extraFilter = tempFilter.substring(0,tempFilter.length()-1);
            extraFilter+= ')';

            qString+= extraFilter;
        }

        qString+= ' order by Product2.Name';
        qString+= ' limit 101';

        system.debug('qString:' +qString);        
        AvailableProducts = database.query(qString);

        // We only display up to 100 results... if there are more than we let the user know (see vf page)
        if(AvailableProducts.size()==101){
            AvailableProducts.remove(100);
            overLimit = true;
        }
        else{
            overLimit=false;
        }
    }

    public void addToShoppingCart(){

        // This function runs when a user hits "select" button next to a product

        for(PricebookEntry d : AvailableProducts){
            if((String)d.Id==toSelect){
                shoppingCart.add(new opportunityLineItem(OpportunityId=theOpp.Id, PriceBookEntry=d, PriceBookEntryId=d.Id, UnitPrice=d.UnitPrice));
                break;
            }
        }

        updateAvailableList();  
    }


    public PageReference removeFromShoppingCart(){

        // This function runs when a user hits "remove" on an item in the "Selected Products" section

        Integer count = 0;

        for(opportunityLineItem d : shoppingCart){
            if((String)d.PriceBookEntryId==toUnselect){

                if(d.Id!=null)
                    forDeletion.add(d);

                shoppingCart.remove(count);
                break;
            }
            count++;
        }

        updateAvailableList();

        for(opportunityLineItem oli : shoppingCart){
            if(oli.ENT_Portfolio__c == true || portfolioChk == true){
                portfolioChk = true;
            }
            if(oli.PriceBookEntry.Product2.ProductCode == 'PREM_PORTFOLIO' || portfolio == true){
                portfolio = true;
            }
        }

        return null;
    }

    public PageReference onSave(){

        portfolio = false;
        portfolioChk = false;


        for(opportunityLineItem oli : shoppingCart){
            if(oli.ENT_Portfolio__c == true || portfolioChk == true){
                portfolioChk = true;
            }
            if(oli.PriceBookEntry.Product2.ProductCode == 'PREM_PORTFOLIO' || portfolio == true){
                portfolio = true;
            }
        }

        // If previously selected products are now removed, we need to delete them
        if(forDeletion.size()>0)
            delete(forDeletion);

        if(portfolio == true && portfolioChk == false){
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'You need to add the associated Products and Rates for your Portfolio deal') );
            return null;
        }
        if(portfolio == false && portfolioChk == true){
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'You need to add the Portfolio Product and Sale Price with the value of your portfolio deal') );
            return null;
        }


        // Previously selected products may have new quantities and amounts, and we may have new products listed, so we use upsert here
        try{
            if(shoppingCart.size()>0)
                upsert(shoppingCart);
        }
        catch(Exception e){
            ApexPages.addMessages(e);
            return null;
        }  

        // After save return the user to the Opportunity
        return new PageReference('/' + ApexPages.currentPage().getParameters().get('Id'));
    }

    public PageReference onCancel(){

        // If user hits cancel we commit no changes and return them to the Opportunity   
        return new PageReference('/' + ApexPages.currentPage().getParameters().get('Id'));
    }

    public PageReference changePricebook(){

        // This simply returns a PageReference to the standard Pricebook selection screen
        // Note that is uses retURL parameter to make sure the user is sent back after they choose

        PageReference ref = new PageReference('/oppitm/choosepricebook.jsp');
        ref.getParameters().put('id',theOpp.Id);
        ref.getParameters().put('retURL','/apex/VF_opportunityProductEntry?id=' + theOpp.Id);

        return ref;
    }
}

closed as off-topic by glls, Oleksandr Berehovskyi, battery.cord, Brian Mansfield, Santanu Boral Sep 14 '17 at 3:08

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Questions on problems in code you've written must describe the specific problem and include valid code to reproduce it. For help writing short, self-contained syntactically-valid examples, see: SSCCE.org" – glls, Oleksandr Berehovskyi, battery.cord, Brian Mansfield, Santanu Boral
If this question can be reworded to fit the rules in the help center, please edit the question.

  • 3
    So...which line is 91? – Adrian Larson Sep 13 '17 at 18:00
  • @adrian Larson -- Sorry, I added the error lines. – John Neilan Sep 13 '17 at 18:16
  • 1
    assert is the oppty is getting inserted, your code is running under the assumption that all the inserts are working fine. – Rao Sep 13 '17 at 18:32
  • 1
    Your problem is theBook is null. – Adrian Larson Sep 13 '17 at 18:52
  • Thanks. So do I need to change something in my controller or in my test class? – John Neilan Sep 13 '17 at 20:22
2

As Adrian mentioned, in the comment, your problem is empty theBook property.

The issue is that you are not creating any Pricebook2 in you test class.


Explanation

No Pricebook2 record is created in the scope of your test class. activepbs.size() will return 0 and activepbs List will contain null.

Pricebook2[] activepbs = [select Id, Name from Pricebook2 where isActive = true limit 2];
if(activepbs.size() == 2){ // No Pricebook2 created in the test, size is 0
    forcePricebookSelection = true;
    theBook = new Pricebook2();
}
else{
    theBook = activepbs[0]; // Selecting the first Pricebook2
}

Because of no Pricebook2 records the else block is executed. You are trying to assign the first activepbs item to theBook property. activepbs[0] is null that means theBook will aslo be null. And that's why you are getting NullPointerException later in the code.

Also take a look at the declaration of Opportunities in the test.

// This Opportunity is missing Pricebook2 assign
Opportunity opp = new Opportunity(AccountId = acc.Id,Contact__c = con.Id,Name = 'Test Account - Opp',CloseDate = date.today(),StageName = 'Order Pending',Type = 'New',CurrencyIsoCode = 'USD');
insert opp;

// Selection of Pricebook2.Id from Opportunity that has none (the previous one)
Opportunity opp2 = [SELECT Pricebook2.Id FROM Opportunity WHERE Id =: opp.Id];
insert opp2;

Fix

You need to insert any Pricebook2 record in the test class and then assign it to Opportunities. To create Pricebook2 you can use great workaround from Ben Edwards (source: Salesforce Developers Forum).

Pricebook2 myPricebook = new Pricebook2(
    Id = Test.getStandardPricebookId(),
    IsActive = true
);

update myPricebook;
myPricebook = [SELECT IsStandard FROM Pricebook2 WHERE Id = :myPricebook.Id];

// Create Opportunity with Pricebook2
Opportunity oppWithPricebook = new Opportunity(Pricebook2Id = myPricebook.Id);
insert oppWithPricebook;
  • Thanks for the explanation and fix, however, I am still getting the same errors. – John Neilan Sep 13 '17 at 23:37
  • 1
    I think I'm running into the errors because there is a process that runs in our instance which automatically assigns a custom price book depending upon the Opportunity record type. – John Neilan Sep 14 '17 at 14:24
  • It could be. The problem is, it assigns Pricebook2Id but that Pricebook2 record is not visible in your class (it's not in the test scope). I don't know the process. Does it run after the update of Opportunity? You can try to create the custom Pricebook2 record and after inserting Opportunity do update of that Opportunity with the new Pricebook2. – Michal Vavra Sep 14 '17 at 14:51

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