1

I am creating a cross-objects trigger that whenever a new opportunity is opened, the trigger goes to check whether the company has sold the same product to the customer before; if so, the type of the opportunity will be 'Existing', otherwise it will be 'New'.

trigger InOfficeOp on Opportunity (after insert, after update) {

    Set<Id> account_id = new Set<Id>();
    List<Id> opp_id = new List<Id>();
    for (Opportunity opp: Trigger.new){
        if(opp.HasOpportunityLineItem){            
            account_id.add(opp.AccountId);
            opp_id.add(opp.Id);
        }

    }


    Map<Id, List<Asset>> acc_asset_map = new Map<Id, List<Asset>>();
    Map<Id, List<OpportunityLineItem>> opp_prod_map = new Map<Id, List<OpportunityLineItem>>();

    for(Asset ast: [SELECT  Product2Id, AccountId FROM Asset WHERE AccountId IN: account_id]) {
        if(acc_asset_map!=null && acc_asset_map.containsKey(ast.Product2Id)) {
            List<Asset> asset_List = acc_asset_map.get(ast.Product2Id);
            asset_List.add(ast);
            acc_asset_map.put(ast.Product2Id,asset_List);
        }
        else {
            acc_asset_map.put(ast.Product2Id, new List<Asset>{ast});
            }
    }

    for (OpportunityLineItem oli: [SELECT Product2Id, OpportunityId FROM OpportunityLineItem WHERE OpportunityId IN: opp_id]){
        if(opp_prod_map!=null && opp_prod_map.containsKey(oli.Product2Id)){
            List<OpportunityLineItem> oppLineItem_Recd = opp_prod_map.get(oli.Product2Id);
            oppLineItem_Recd.add(oli);
            opp_prod_map.put(oli.Product2Id, oppLineItem_Recd);        
        }
        else {
            opp_prod_map.put(oli.Product2Id, new List<OpportunityLineItem>{oli});
         }
    }
    Id op;
    for(Id prodId: acc_asset_map.keyset()){
        if(opp_prod_map!= null && opp_prod_map.containsKey(prodId)) {
            for(OpportunityLineItem oppLine_Recd:opp_prod_map.get(prodId)) {
                op = oppLine_Recd.OpportunityId;
            }
        }
    }
    for(Opportunity o:Trigger.new){
        if(o.Id == op){
            o.Type = 'Existing_Business';
        }
        else{
            o.Type = 'New_Business';
        }
    }    
}

The above is my trigger code.

And here is my test:

@isTest
private class OppTriggerTest{
    static void PopulateExtensiontest() 
    {
        Date closeDt = Date.Today();
        date myDate = date.today();

        Account a2 = new Account(Name='icrm test acc');
        insert a2;

        Opportunity oppr = new Opportunity(Name='testing DIE 6/4/2017', AccountId =
a2.Id,StageName='Prodpecting', 
CloseDate = Date.Today());
        insert oppr;

        Pricebook2 pb22 = new Pricebook2(Name='testDIE');
        insert pb22;

        Product2 pro2 = new Product2(Name='BXCD', isActive=true);
        insert pro2;

        PricebookEntry pbe2 =new 
PricebookEntry(unitprice=0.01,Product2Id=pro2.Id, Pricebook2Id=Test.getStandardPricebookId(), isActive=true, UseStandardPrice=false);

        insert pbe2;

        OpportunityLineItem OPplineitem2 = new OpportunityLineItem (Quantity=2,
OpportunityId=oppr.Id, UnitPrice=0.01, PriceBookEntryId =pbe2.Id);
        insert OPplineitem2;

        update oppr;
    }
}

And I got the error message after I exceture the test:

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, InOfficeOp: execution of AfterInsert caused by: System.FinalException: Record is read-only
Trigger.InOfficeOp: line 51, column 1: []

I don't quite understand what does the message mean. Could anyone please help me what does the error mean? Is my test wrong?

3

If you want to update fields on trigger records, you should use before events. From the Apex Developer Guide on Triggers:

There are two types of triggers:

  • Before triggers are used to update or validate record values before they’re saved to the database.
  • After triggers are used to access field values that are set by the system (such as a record's Id or LastModifiedDate field), and to affect changes in other records, such as logging into an audit table or firing asynchronous events with a queue. The records that fire the after trigger are read-only.

Note that last sentence:

The records that fire the after trigger are read-only.


So that's what's wrong with your trigger. At first glance, anyway. It seems extremely unlikely to perform according to spec.

As far as your test method goes, you need to tell the platform that it should be executed as a test:

static testmethod void myTest1()
{
    // this syntax is my preference
}
@IsTest
static void myTest2()
{
    // this syntax is also valid
}
  • Hi Adrian, yeah, I just realized that and I changed it to before. I execute the test again, but there is 0/0 test methods passed. I never wrote unit-test before, is there anything wrong with my test? Thank you very much! – xybcss Apr 7 '17 at 20:53
  • @xybcss You didn't mark your test method as testmethod... – Adrian Larson Apr 7 '17 at 20:55
  • Thank you so much Adrian! I just passed one test method. How can I check whether my test covered 75% of the code? Does it explicitly show? Also, could you elaborate what do you mean "seems extremely unlikely to perform according to spec."? Do you mean if my trigger is 'after update, after insert' then it won't work? – xybcss Apr 7 '17 at 21:23
  • I mean your logic isn't really coherent as written and doesn't look like it would match any reasonable spec. It's hard to say as your requirement isn't crystal clear in your question, as written. Regardless, you should really look at some Trailhead modules. Coverage is far from the most important aspect of testing, you should focus on assertions and verifying application behavior. There are a few different ways to check coverage including the Developer Console. – Adrian Larson Apr 7 '17 at 21:42

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.