+ Start a Discussion
Alaric WimerAlaric Wimer 

Why is my test class assertion failing?

I have a trigger that updates the quantity of one opportunity product when another one's quantity is updated. Everything seems to be working when I mess around in the sandbox. However, the last test class assertion keeps failing. Does anyone know why it's failing? Any help is greatly appreciated.

Here's my trigger:

trigger UpdateInverterQuantity on OpportunityLineItem (before update) {
    Map<Id, Product2> prodMap = new Map<Id, Product2>([
        SELECT Id, Name 
          FROM Product2
         WHERE Name = 'Heliene 60M-BLK 310W' 
            OR Name = 'Heliene 60M-HBLK 300W'
    ]); 

    for (OpportunityLineItem oli : Trigger.new) {

        // Get related inverter from same opportunity
        List<OpportunityLineItem> inverters = [ 
            SELECT Id, Quantity, Product2.Name 
              FROM OpportunityLineItem
             WHERE OpportunityId = :oli.OpportunityId
               AND (Product2.Name = 'APSystems QS1'
                OR Product2.Name = 'YC600') 
             LIMIT 1
        ];

        if (prodMap.ContainsKey(oli.Product2Id)) { // panel 1 or panel 2
            // Get quantity of panels
            if (!inverters.isEmpty()) {
                // set inverter quantity formulas
                Decimal inverterQuantityOne = (inverters.get(0).Quantity / 4);
                Decimal inverterQuantityTwo = (inverters.get(0).Quantity / 2);
                inverterQuantityOne = inverterQuantityOne.round(System.RoundingMode.CEILING);
                inverterQuantityTwo = inverterQuantityTwo.round(System.RoundingMode.CEILING);

                // update quantity of inverter
                if (inverters.get(0).Product2.Name == 'APSystems QS1') { // inverter 1  
                    inverters.get(0).Quantity = inverterQuantityOne;
                } else if (inverters.get(0).Product2.Name == 'YC600') { // inverter 2
                    inverters.get(0).Quantity = inverterQuantityTwo;
                }
                update inverters;
            }
        }
    }
}


And here's my test class:

@isTest
private class UpdateInverterQuantityTest {
    @isTest static void updatePanelQuantity() {
        // create an opportunity
        Opportunity myOpp  = new Opportunity();
        myOpp.Name         = 'Test';
        myOpp.StageName    = 'Closed Won';
        myOpp.CloseDate    = Date.today();
        myOpp.Pricebook2Id = Test.getStandardPricebookId();
        insert myOpp;

        Id pricebookId = Test.getStandardPricebookId();

        //Create your panel
        Product2 panel = new Product2(
            Name        = 'Heliene 60M-HBLK 300W',
            ProductCode = 'H300W',
            isActive    = true
        );
        insert panel;

        //Create your inverter
        Product2 firstInverter = new Product2(
            Name        = 'APSystems QS1',
            ProductCode = 'APS',
            isActive    = true
        );
        insert firstInverter;

        //Create your pricebook entry for panel
        PricebookEntry  panelEntry = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id   = panel.Id,
            UnitPrice    = 100.00,
            IsActive     = true
        );
        insert panelEntry;

        //Create your pricebook entry for inverter
        PricebookEntry inverterEntry = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id   = firstInverter.Id,
            UnitPrice    = 100.00,
            IsActive     = true
        );
        insert inverterEntry;

        // add panel to opp products
        OpportunityLineItem oli = new OpportunityLineItem(
            OpportunityId    = myOpp.Id,
            Quantity         = 20,
            PricebookEntryId = panelEntry.Id,
            TotalPrice       = 20 * panelEntry.UnitPrice
        );
        insert oli;

        // add inverter to opp products
        OpportunityLineItem oli2 = new OpportunityLineItem(
            OpportunityId    = myOpp.Id,
            Quantity         = 20,
            PricebookEntryId = inverterEntry.Id,
            TotalPrice       = 20 * inverterEntry.UnitPrice
        );
        insert oli2;

        // Get updated inverter quantity
        OpportunityLineItem inverter = [
            SELECT Quantity
              FROM OpportunityLineItem
             WHERE OpportunityId = :oli.OpportunityId
               AND Id = :oli2.Id
             LIMIT 1
        ];

        System.assertEquals(5, inverter.Quantity);

        // Update quantity of panels
        oli.Quantity = 40;
        update oli;

        // Get updated quantity of inverters
        OpportunityLineItem updatedInverter = [
            SELECT Quantity
              FROM OpportunityLineItem
             WHERE OpportunityId = :oli.OpportunityId
               AND Id = :oli2.Id
             LIMIT 1
        ];

        System.assertEquals(10, updatedInverter.Quantity);
    }
}
Best Answer chosen by Alaric Wimer
Andrew GAndrew G
Ok, so i did a play with the trigger - note that I haven't moved it to a handler yet, which i would normally do with something complex

I'm not sold on the logic, but basically,
if i'm inserting a Panel OLI go find any Invertor OLIs and update that number.
if i'm inserting an invertor OLI go find any Panel OLIs and cacluclate my number
trigger UpdateInverterQuantity on OpportunityLineItem (before insert, before update) {
    Map<Id, Product2> panelProdMap = new Map<Id, Product2>([
        SELECT Id, Name 
          FROM Product2
         WHERE Name = 'Heliene 60M-BLK 310W' 
            OR Name = 'Heliene 60M-HBLK 300W'
    ]); 
    Map<Id, Product2> invProdMap = new Map<Id, Product2>([
        SELECT Id, Name 
          FROM Product2
         WHERE Name = 'APSystems QS1' 
            OR Name = 'YC600'
    ]); 

    list<String> oppIds = new List<String>();
    for (OpportunityLineItem oli : Trigger.new ) {
        oppIds.add(oli.OpportunityId);
    }
    list<OpportunityLineItem> panelOLIs = new list<OpportunityLineItem>();
    panelOLIs = [SELECT Id, OpportunityId, Quantity, Product2.Name 
                    FROM OpportunityLineItem
                    WHERE OpportunityId IN :oppIds
                    AND Product2Id IN :panelProdMap.keySet()];
    Map<Id,OpportunityLineItem> panelOLImap = new Map<Id, OpportunityLineItem>();
    for (OpportunityLineItem oli : panelOLIs) {
        panelOLImap.put(oli.opportunityId, oli);
    }

    list<OpportunityLineItem> invOLIs = new list<OpportunityLineItem>();
    invOLIs = [SELECT Id, OpportunityId, Quantity, Product2.Name  
                    FROM OpportunityLineItem
                    WHERE OpportunityId IN :oppIds
                    AND Product2Id IN :invProdMap.keySet()];
    Map<Id,OpportunityLineItem> invOLImap = new Map<Id, OpportunityLineItem>();
    for (OpportunityLineItem oli : invOLIs) {
        invOLImap.put(oli.opportunityId, oli);
    }

    Integer divisor;
    Integer panelQty;
    OpportunityLineItem tempOLI;
    List<OpportunityLineItem> toUpdate = new List<OpportunityLineItem>();

    for (OpportunityLineItem oli : Trigger.new ) {
        System.debug('DEBUG - FOR LOOP');
        if (panelProdMap.ContainsKey(oli.Product2Id )) {   //inserting/updating a panel oli
            System.debug('DEBUG - insert/update Panel');
            if ( panelProdMap.get(oli.Product2Id).Name == 'Heliene 60M-HBLK 300W' ) {
                divisor = 4;
            } else if ( panelProdMap.get(oli.Product2Id).Name == 'Heliene 60M-BLK 310W' ) {
                divisor = 2;
            } else {
                divisor = 1;
            }
            if (invOLImap.ContainsKey(oli.OpportunityId )) {
                System.debug('DEBUG - found previous invertors');
                tempOLI = invOLImap.get(oli.OpportunityId );     //we have a matching panel OLI
                Decimal inverterQuantity = (oli.Quantity / divisor); 
                inverterQuantity = inverterQuantity.round(System.RoundingMode.CEILING); 
                if (tempOLI.Quantity <> inverterQuantity ) {
                     tempOLI.Quantity = inverterQuantity;
                     toUpdate.add(tempOLI);
                }
            } 
        }
        if (invProdMap.ContainsKey(oli.Product2Id )) {     //inserting/updating a invertor oli
            if (trigger.isUpdate && (oli.Quantity <> trigger.oldMap.get(oli.Id).Quantity) ) {
                System.debug('DEBUG - update Invertor with old map');
                //quantity has been changed on the invertor - do something else
            } else {
                System.debug('DEBUG - insert Invertor');
                if ( invProdMap.get(oli.Product2Id).Name == 'APSystems QS1' ) {
                    divisor = 4;
                } else if ( invProdMap.get(oli.Product2Id).Name == 'YC600' ) {
                    divisor = 2;
                } else {
                    divisor = 1;
                }
                if (panelOLImap.ContainsKey(oli.OpportunityId )) {
                    System.debug('DEBUG - found previous panels');
                    tempOLI = panelOLImap.get(oli.OpportunityId );     //we have a matching panel OLI
                    Decimal inverterQuantity = (tempOLI.Quantity / divisor); 
                    inverterQuantity = inverterQuantity.round(System.RoundingMode.CEILING); 
                    if (oli.Quantity <> inverterQuantity ) {
                         oli.Quantity = inverterQuantity;
                    }
                }
            }
        }
    }
    if (toUpdate.size() > 0  ) {
        update toUpdate;
    }
}
I also updated part of your test class  - not sure why you were playing with PriceBookEntry in the OLI.

Note that it passes the insert test as well.  
 
@isTest
private class UpdateInverterQuantityTest {
    @isTest static void updatePanelQuantity() {
        // create an opportunity
        Opportunity myOpp  = new Opportunity();
        myOpp.Name         = 'Test';
        myOpp.StageName    = 'Closed Won';
        myOpp.CloseDate    = Date.today();
        myOpp.Pricebook2Id = Test.getStandardPricebookId();
        insert myOpp;

        Id pricebookId = Test.getStandardPricebookId();

        //Create your panel
        Product2 panel = new Product2(
            Name        = 'Heliene 60M-HBLK 300W',
            ProductCode = 'H300W',
            isActive    = true
        );
        insert panel;

        //Create your inverter
        Product2 firstInverter = new Product2(
            Name        = 'APSystems QS1',
            ProductCode = 'APS',
            isActive    = true
        );
        insert firstInverter;

        //Create your pricebook entry for panel
        PricebookEntry  panelEntry = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id   = panel.Id,
            UnitPrice    = 100.00,
            IsActive     = true
        );
        insert panelEntry;

        //Create your pricebook entry for inverter
        PricebookEntry inverterEntry = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id   = firstInverter.Id,
            UnitPrice    = 100.00,
            IsActive     = true
        );
        insert inverterEntry;

         // add panel to opp products
        OpportunityLineItem oli = new OpportunityLineItem(
            OpportunityId    = myOpp.Id,
            Quantity         = 20,
            Product2Id = panelEntry.Id,
            TotalPrice       = 20 * panelEntry.UnitPrice
        );
        insert oli;
        System.debug('TEST DEBUG - Inserted panel oli');

        // add inverter to opp products
        OpportunityLineItem oli2 = new OpportunityLineItem(
            OpportunityId    = myOpp.Id,
            Quantity         = 20,
            Product2Id = inverterEntry.Id,
            TotalPrice       = 20 * inverterEntry.UnitPrice
        );
        insert oli2;
        System.debug('TEST DEBUG - Inserted inverter oli');

        // Get updated inverter quantity
        OpportunityLineItem inverter = [
            SELECT Quantity
              FROM OpportunityLineItem
             WHERE OpportunityId = :oli.OpportunityId
               AND Id = :oli2.Id
             LIMIT 1
        ];

        System.assertEquals(5, inverter.Quantity);

        // Update quantity of panels
        oli.Quantity = 40;
        update oli;

        // Get updated quantity of inverters
        OpportunityLineItem updatedInverter = [
            SELECT Quantity
              FROM OpportunityLineItem
             WHERE OpportunityId = :oli.OpportunityId
               AND Id = :oli2.Id
             LIMIT 1
        ];

        System.assertEquals(10, updatedInverter.Quantity);
    }
}

Regards
Andrew
 

All Answers

Andrew GAndrew G
Ok, reading the code, I think it will be failing at the first assertion:
System.assertEquals(5, inverter.Quantity);
Reason:
Your trigger is before update.  The code will not run to update the invertor numbers on insert, hence it will fail there

Regards
Andrew

note: you do have a SELECT statement in a FOR loop, bit of a no-no.

 
Alaric WimerAlaric Wimer

Thank you Andrew. There is another trigger that handles the insert logic so that seems to work. I have deleted that assertion anyway now, and it still seems to fail. The error that I get says, "Assertion Failed: Expected: 10, Actual: 5.00".

Also, do you have any suggestions as to how I can move that SOQL query outside of the for loop? The only reason I have it in there is because i needed to reference a bind variable:

WHERE OpportunityId = :oli.OpportunityId
Thanks again for your help. I'm very new to this so I appreciate you steering me in the right direction.
Andrew GAndrew G
Ok, the logic in your test class looks good.  so therefore the issue is in the Trigger.

Before I bend my mind to this, a question - is this code a learning aspect only or are you planning to release this code into a production environment?

The reason I ask is that there are a heap of variables to be considered:.

1.  Will panels always be inserted first?
2.  Will you have oppty where there are more than the one type of panel or invertor?
3.  Will more than one oppty line item contain the same product?
that's the ones that jump out.

Depending on your answer, it will drive how I would create the trigger.  And therefore the complexity and time it takes.  And noting the complexity I would then go to a trigger with a trigger handler.

Regards
Andrew
Andrew GAndrew G
And I worked out the fault.
It is a before insert , so therefore the record being updated does not exist in the database, and therefore the query is querying the records as they exist "now" in the database.  So when you query the database, the quantity is 20.  And that is the value that you use in the calculation as opposed to the value in the now being updated records.

You could test this by doing a simple change 

Decimal inverterQuantityOne = (inverters.get(0).Quantity / 4); 
Decimal inverterQuantityTwo = (inverters.get(0).Quantity / 2); 

change to 

Decimal inverterQuantityOne = (oli.Quantity / 4);
Decimal inverterQuantityTwo = (oli.Quantity / 2);
And as an aside, I would do that entire section as:
Integer divisor;
Boolean isInvertor;
divisor =1;  //so we don't get weird errors dividing by zero
if (oli.Product2.Name == 'APSystems QS1') { // inverter 1  
       divisor = 4;
    isInvertor = TRUE;
} else if (oli.Product2.Name == 'YC600') { // inverter 2
      divisor = 2;
    isInvertor = TRUE;
}
if isINvertor {
Decimal inverterQuantity = (oli.Quantity / divisor); 
inverterQuantity = inverterQuantity.round(System.RoundingMode.CEILING); 
if( oli.quantity <> inverterQuantity ) {
    oli.Quantity = inverterQuantity;
}
}
the change is the divisor only -  the rest of the logic remains the same, so why not create a variable for the divisor and write the other lines once?



Regards
Andrew
Andrew GAndrew G
And one further detail - best practice is to have one trigger per object.  Where there are multiple triggers, there is no guarentee that in what order the triggers will execute.  So if one trigger is setting up data that is required in another trigger, you may get unusual behaviours or triggers failing to fire correctly.

Regards
Andrew
Alaric WimerAlaric Wimer

Hey Andrew, thank you for the code revision. The divisor logic makes perfects sense and I'll be trying it out soon. Also, yes I am planning on releasing this code into a production environment (first time doing so if you couldn't already tell...). It is a brand new instance as we are just starting.

Here's some answers to your questions:

1.  Will panels always be inserted first?
I was hoping to get this to work two ways. First, if panels have been already inserted. And second, if panels and inverters are inserted at the same time. I don't see a situation where inverters will be inserted first.


2.  Will you have oppty where there are more than the one type of panel or invertor?
No, every opportunity will have one type of panel and one type inverter.


3.  Will more than one oppty line item contain the same product?
I would say no for now.

Andrew GAndrew G
Ok, so i did a play with the trigger - note that I haven't moved it to a handler yet, which i would normally do with something complex

I'm not sold on the logic, but basically,
if i'm inserting a Panel OLI go find any Invertor OLIs and update that number.
if i'm inserting an invertor OLI go find any Panel OLIs and cacluclate my number
trigger UpdateInverterQuantity on OpportunityLineItem (before insert, before update) {
    Map<Id, Product2> panelProdMap = new Map<Id, Product2>([
        SELECT Id, Name 
          FROM Product2
         WHERE Name = 'Heliene 60M-BLK 310W' 
            OR Name = 'Heliene 60M-HBLK 300W'
    ]); 
    Map<Id, Product2> invProdMap = new Map<Id, Product2>([
        SELECT Id, Name 
          FROM Product2
         WHERE Name = 'APSystems QS1' 
            OR Name = 'YC600'
    ]); 

    list<String> oppIds = new List<String>();
    for (OpportunityLineItem oli : Trigger.new ) {
        oppIds.add(oli.OpportunityId);
    }
    list<OpportunityLineItem> panelOLIs = new list<OpportunityLineItem>();
    panelOLIs = [SELECT Id, OpportunityId, Quantity, Product2.Name 
                    FROM OpportunityLineItem
                    WHERE OpportunityId IN :oppIds
                    AND Product2Id IN :panelProdMap.keySet()];
    Map<Id,OpportunityLineItem> panelOLImap = new Map<Id, OpportunityLineItem>();
    for (OpportunityLineItem oli : panelOLIs) {
        panelOLImap.put(oli.opportunityId, oli);
    }

    list<OpportunityLineItem> invOLIs = new list<OpportunityLineItem>();
    invOLIs = [SELECT Id, OpportunityId, Quantity, Product2.Name  
                    FROM OpportunityLineItem
                    WHERE OpportunityId IN :oppIds
                    AND Product2Id IN :invProdMap.keySet()];
    Map<Id,OpportunityLineItem> invOLImap = new Map<Id, OpportunityLineItem>();
    for (OpportunityLineItem oli : invOLIs) {
        invOLImap.put(oli.opportunityId, oli);
    }

    Integer divisor;
    Integer panelQty;
    OpportunityLineItem tempOLI;
    List<OpportunityLineItem> toUpdate = new List<OpportunityLineItem>();

    for (OpportunityLineItem oli : Trigger.new ) {
        System.debug('DEBUG - FOR LOOP');
        if (panelProdMap.ContainsKey(oli.Product2Id )) {   //inserting/updating a panel oli
            System.debug('DEBUG - insert/update Panel');
            if ( panelProdMap.get(oli.Product2Id).Name == 'Heliene 60M-HBLK 300W' ) {
                divisor = 4;
            } else if ( panelProdMap.get(oli.Product2Id).Name == 'Heliene 60M-BLK 310W' ) {
                divisor = 2;
            } else {
                divisor = 1;
            }
            if (invOLImap.ContainsKey(oli.OpportunityId )) {
                System.debug('DEBUG - found previous invertors');
                tempOLI = invOLImap.get(oli.OpportunityId );     //we have a matching panel OLI
                Decimal inverterQuantity = (oli.Quantity / divisor); 
                inverterQuantity = inverterQuantity.round(System.RoundingMode.CEILING); 
                if (tempOLI.Quantity <> inverterQuantity ) {
                     tempOLI.Quantity = inverterQuantity;
                     toUpdate.add(tempOLI);
                }
            } 
        }
        if (invProdMap.ContainsKey(oli.Product2Id )) {     //inserting/updating a invertor oli
            if (trigger.isUpdate && (oli.Quantity <> trigger.oldMap.get(oli.Id).Quantity) ) {
                System.debug('DEBUG - update Invertor with old map');
                //quantity has been changed on the invertor - do something else
            } else {
                System.debug('DEBUG - insert Invertor');
                if ( invProdMap.get(oli.Product2Id).Name == 'APSystems QS1' ) {
                    divisor = 4;
                } else if ( invProdMap.get(oli.Product2Id).Name == 'YC600' ) {
                    divisor = 2;
                } else {
                    divisor = 1;
                }
                if (panelOLImap.ContainsKey(oli.OpportunityId )) {
                    System.debug('DEBUG - found previous panels');
                    tempOLI = panelOLImap.get(oli.OpportunityId );     //we have a matching panel OLI
                    Decimal inverterQuantity = (tempOLI.Quantity / divisor); 
                    inverterQuantity = inverterQuantity.round(System.RoundingMode.CEILING); 
                    if (oli.Quantity <> inverterQuantity ) {
                         oli.Quantity = inverterQuantity;
                    }
                }
            }
        }
    }
    if (toUpdate.size() > 0  ) {
        update toUpdate;
    }
}
I also updated part of your test class  - not sure why you were playing with PriceBookEntry in the OLI.

Note that it passes the insert test as well.  
 
@isTest
private class UpdateInverterQuantityTest {
    @isTest static void updatePanelQuantity() {
        // create an opportunity
        Opportunity myOpp  = new Opportunity();
        myOpp.Name         = 'Test';
        myOpp.StageName    = 'Closed Won';
        myOpp.CloseDate    = Date.today();
        myOpp.Pricebook2Id = Test.getStandardPricebookId();
        insert myOpp;

        Id pricebookId = Test.getStandardPricebookId();

        //Create your panel
        Product2 panel = new Product2(
            Name        = 'Heliene 60M-HBLK 300W',
            ProductCode = 'H300W',
            isActive    = true
        );
        insert panel;

        //Create your inverter
        Product2 firstInverter = new Product2(
            Name        = 'APSystems QS1',
            ProductCode = 'APS',
            isActive    = true
        );
        insert firstInverter;

        //Create your pricebook entry for panel
        PricebookEntry  panelEntry = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id   = panel.Id,
            UnitPrice    = 100.00,
            IsActive     = true
        );
        insert panelEntry;

        //Create your pricebook entry for inverter
        PricebookEntry inverterEntry = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id   = firstInverter.Id,
            UnitPrice    = 100.00,
            IsActive     = true
        );
        insert inverterEntry;

         // add panel to opp products
        OpportunityLineItem oli = new OpportunityLineItem(
            OpportunityId    = myOpp.Id,
            Quantity         = 20,
            Product2Id = panelEntry.Id,
            TotalPrice       = 20 * panelEntry.UnitPrice
        );
        insert oli;
        System.debug('TEST DEBUG - Inserted panel oli');

        // add inverter to opp products
        OpportunityLineItem oli2 = new OpportunityLineItem(
            OpportunityId    = myOpp.Id,
            Quantity         = 20,
            Product2Id = inverterEntry.Id,
            TotalPrice       = 20 * inverterEntry.UnitPrice
        );
        insert oli2;
        System.debug('TEST DEBUG - Inserted inverter oli');

        // Get updated inverter quantity
        OpportunityLineItem inverter = [
            SELECT Quantity
              FROM OpportunityLineItem
             WHERE OpportunityId = :oli.OpportunityId
               AND Id = :oli2.Id
             LIMIT 1
        ];

        System.assertEquals(5, inverter.Quantity);

        // Update quantity of panels
        oli.Quantity = 40;
        update oli;

        // Get updated quantity of inverters
        OpportunityLineItem updatedInverter = [
            SELECT Quantity
              FROM OpportunityLineItem
             WHERE OpportunityId = :oli.OpportunityId
               AND Id = :oli2.Id
             LIMIT 1
        ];

        System.assertEquals(10, updatedInverter.Quantity);
    }
}

Regards
Andrew
 
This was selected as the best answer
Alaric WimerAlaric Wimer

Andrew, you have been an absolute lifesaver. I can't thank you enough. The trigger and test class check out on my end. I'm gonna have to read through the code a few more times so I can understand how it's working and learn from my past mistakes. As for your comment about the Pricebook entry in the OLI, I simply must have thought it was a required field. But everything seems to be working fine without it, so clearly I was mistaken.

Andrew GAndrew G
No worries

Next thing for you to play with is creating a Handler class, which I would recommend.  I generally keep my triggers as simple as possible and do the work with handlers.
Here's an example I did on another post that may prove helpful

https://developer.salesforce.com/forums/ForumsMain?id=9062I000000IK8dQAG

Have fun
Regards
Andrew
Alaric WimerAlaric Wimer
Andrew, I'm now really curious about making this work if Inverters were added first. Would you be able to point me in the right direction?