You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Contact触发器处理器未触发错误,SOQL查询异常排查求助

问题描述

我需要在Contact的before update触发器触发时,将Trigger.new列表传入ContactTriggerHandler类,筛选出属于特定记录类型且关联了具备指定角色的AccountContactRelation的Contact记录,对这些记录进行字段非空校验以及激活状态校验。目前代码可正常编译,但校验错误完全不触发;如果直接遍历传入的con列表(不做记录类型和关联关系校验),功能能正常运行,因此怀疑SOQL查询存在问题,且测试类运行后仅获得63%的代码覆盖率。

触发器代码

trigger ContactTrigger on Contact (before update) {
    if(trigger.isbefore){
        if(trigger.isupdate){
          ContactTriggerHandler.IsPricingLetter(Trigger.new);
        }//End If IsUpdate          
    }//End If IsBefore
}//End Class

处理器类代码

public class ContactTriggerHandler {
    public static void IsPricingLetter(List<Contact> con){
                            
       List<Contact> ContactLookup = [SELECT id,mailingstreet,mailingcity,mailingstate,mailingpostalcode,mailingcountry,email,active_contact__c                                     
                                       FROM contact WHERE id IN :con 
                                       AND id IN (SELECT Contactid 
                                                  FROM accountcontactrelation
                                                  WHERE roles INCLUDES (:label.messer_US_Pricing_Letter) AND isactive=true)
                                       AND Recordtypeid='0125b0000015OkJAAU'
                                      ];
            
          for(Contact CheckContact : ContactLookup){
                          if(CheckContact.MailingStreet==null){
                          CheckContact.MailingStreet.addError(label.Messer_US_Contact_Street+' on a '+label.messer_US_Pricing_Letter+' Contact cannot be null.'); 
                          }//End If Mailing Street   
                          if(CheckContact.MailingCity==null){
                          CheckContact.MailingCity.addError(label.Messer_US_Contact_City+' on a '+label.messer_US_Pricing_Letter+' Contact cannot be null.'); 
                          }//End If Mailing City
                          if(CheckContact.MailingState==null){
                          CheckContact.MailingState.addError(label.Messer_US_Contact_State+' on a '+label.messer_US_Pricing_Letter+' Contact cannot be null.'); 
                          }//End If Mailing State 
                          if(CheckContact.MailingPostalCode==null){
                          CheckContact.MailingPostalCode.addError(label.Messer_US_Contact_Postal_Code+' on a '+label.messer_US_Pricing_Letter+' Contact cannot be null.'); 
                          }//End If Mailing Postal Code 
                          if(CheckContact.MailingCountry==null){
                          CheckContact.MailingCountry.addError(label.Messer_US_Contact_Country+' on a '+label.messer_US_Pricing_Letter+' Contact cannot be null.'); 
                          }//End If Mailing Country
                          if(CheckContact.Email==null){
                          CheckContact.Email.addError(label.Messer_US_Contact_Email+' on a '+label.messer_US_Pricing_Letter+' Contact cannot be null.'); 
                          }//End If Email
                          if(CheckContact.Active_Contact__c==false){
                          CheckContact.active_contact__c.addError('A Contact with an active '+label.messer_US_Pricing_Letter+' relationship cannot be made inactive.'); 
                          }//End If Active
          }//end for loop
                               
    }//end isPricingLetter method
}//End Class

测试类代码

@isTest
public class ContactTriggerHandlerTest {

    public static Account getAccount(){
        ccrz__E_AccountGroup__c accountgroupObj = new ccrz__E_AccountGroup__c(Name = 'Messer Base',
                                                                              CurrencyIsoCode = 'USD',
                                                                              ccrz__PriceListSelectionMethod__c = 'Best Price');
        insert accountgroupObj;
        account a = new account(name='DevTest Account',Type='Prospect',Industry='Chemistry & Energy',
                                              ccrz__E_AccountGroup__C=accountgroupobj.id);

        insert a;
        return a;
    }//End getAccount
    public static Contact getContact(){
        Account a = getAccount();
        contact c = new contact(firstname = 'TestFirstName',active_contact__c=true,lastname = 'TestLastName',mailingstreet = 'Test Street',mailingcity = 'Test City',
                                mailingState = 'Test State',mailingPostalCode = '99999',mailingCountry = 'US',email = 'test@test.com',accountid=a.id
                               ,recordtypeid='0125b0000015OkJAAU');//CRM Contact US Record Type
        insert c;
        return c;      

    }//End getContact
    static testmethod void updateStreetWithPricingLettersRelationship(){
        Contact c = getContact();
        Account a = getAccount();
        
        accountcontactrelation acr = new accountcontactrelation(accountid=a.id,contactid = c.id,Roles = label.messer_US_Pricing_Letter,isActive=true);
        insert acr;     
         
        try{
        c.mailingstreet='';
               update c;
        }catch(DMLException e){
            System.debug(e);
        }finally{
            System.debug('Mailing Street Checked!');
                }
 
        }//End updateStreetwithRelationship
     static testmethod void updateCityWithPricingLettersRelationship(){
        Contact c = getContact();
        Account a = getAccount();
        
        accountcontactrelation acr = new accountcontactrelation(accountid = a.Id,contactid = c.id,Roles = label.messer_US_Pricing_Letter,isActive=true);
        insert acr;  
         
        try{
         c.mailingcity='';
        update c;
        }catch(DMLException e){
            System.debug(e);
        }finally{
            System.debug('Mailing City Checked!');
                }
        
    }//End updateCitywithRelationship
    static testmethod void updateStateWithPricingLettersRelationship(){
        Contact c = getContact();
        Account a = getAccount();
        
        accountcontactrelation acr = new accountcontactrelation(accountid = a.Id,contactid = c.id,Roles = label.messer_US_Pricing_Letter,isActive=true);
        insert acr;  
        
        try{
        c.MailingState='';
        update c;
        }catch(DMLException e){
            System.debug(e);
        }
        finally{
            System.debug('Mailing State Checked!');
                }
    }//End updateStatewithRelationship
    static testmethod void updatePostalCodeWithPricingLettersRelationship(){
        Contact c = getContact();
        Account a = getAccount();
        
        accountcontactrelation acr = new accountcontactrelation(accountid = a.Id,contactid = c.id,Roles = label.messer_US_Pricing_Letter,isActive=true);
        insert acr;   
        
        try{
        c.MailingPostalCode='';
        update c;
        }catch(DMLException e){
            System.debug(e);
        }
        finally{
            System.debug('Mailing Postal Code Checked!');
                }
    }//End updatePostalCodewithRelationship
    static testmethod void updateCountryWithPricingLettersRelationship(){
        Contact c = getContact();
        Account a = getAccount();
        
        accountcontactrelation acr = new accountcontactrelation(accountid = a.Id,contactid = c.id,Roles = label.messer_US_Pricing_Letter,isActive=true);
        insert acr;  
        
        try{
        c.MailingCountry='';
        update c;
        }catch(DMLException e){
            System.debug(e);
        }finally{
            System.debug('Mailing Country Checked!');
                }
        
    }//End updateCountrywithRelationship
    static testmethod void updateEmailWithPricingLettersRelationship(){
        Contact c = getContact();
        Account a = getAccount();
        
        accountcontactrelation acr = new accountcontactrelation(accountid = a.Id,contactid = c.id,Roles = label.messer_US_Pricing_Letter,isActive=true);
        insert acr;  
        
        try{
        c.Email='';
        update c;
        }catch(DMLException e){
            System.debug(e);
        }finally{
            System.debug('Email Checked!');
                }
        
    }//End updateEmailwithRelationship
    static testmethod void DeactivateContactAttempt(){
        Contact c = getContact();
        Account a = getAccount();
        
        accountcontactrelation acr = new accountcontactrelation(accountid = a.Id,contactid = c.id,Roles = label.messer_US_Pricing_Letter,isActive=true);
        insert acr;  
        
        try{
        c.Active_Contact__c=false;
        update c;
        }catch(DMLException e){
            System.debug(e);
        }finally{
            System.debug('Active Contact? Checked!');
                }
        
    }//End updateEmailwithRelationship

}//End Class

问题分析与修复方案

核心问题

  1. SOQL查询的是数据库旧记录而非触发器中的新记录:在before update触发器中,Trigger.new保存的是修改后的Contact记录,但SOQL查询的是数据库中的原始记录(修改前的状态)。比如测试中把mailingstreet设为空,SOQL查出来的还是原来有值的记录,自然不会触发非空校验的addError。
  2. 对查询出的记录添加错误无效:addError必须调用在Trigger上下文的记录(即Trigger.new中的实例)上,才能阻止DML操作并抛出错误。对SOQL查询返回的Contact实例调用addError,这些实例不属于当前触发器上下文,所以不会生效。
  3. 硬编码RecordTypeId:直接使用ID值会导致代码在不同环境(沙盒/生产)中失效,应该通过记录类型名称动态获取ID。
  4. 测试类缺乏断言:仅靠System.debug无法验证错误是否触发,导致测试无法覆盖校验逻辑的分支,进而影响代码覆盖率。

修复后的处理器类代码

public class ContactTriggerHandler {
    public static void IsPricingLetter(List<Contact> con){
        // 动态获取目标记录类型ID
        Id targetRecordTypeId = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('CRM Contact US').getRecordTypeId();
        if(targetRecordTypeId == null){
            return;
        }

        // 先筛选出Trigger.new中符合记录类型的Contact ID
        Set<Id> contactIds = new Set<Id>();
        for(Contact c : con){
            if(c.RecordTypeId == targetRecordTypeId){
                contactIds.add(c.Id);
            }
        }

        if(contactIds.isEmpty()){
            return;
        }

        // 查询这些Contact对应的有效AccountContactRelation
        Map<Id, List<AccountContactRelation>> contactToAcrMap = new Map<Id, List<AccountContactRelation>>();
        for(AccountContactRelation acr : [SELECT ContactId 
                                           FROM AccountContactRelation 
                                           WHERE ContactId IN :contactIds 
                                           AND Roles INCLUDES (:label.messer_US_Pricing_Letter) 
                                           AND IsActive = true]){
            if(!contactToAcrMap.containsKey(acr.ContactId)){
                contactToAcrMap.put(acr.ContactId, new List<AccountContactRelation>());
            }
            contactToAcrMap.get(acr.ContactId).add(acr);
        }

        // 遍历Trigger.new中的记录,对符合条件的记录添加校验错误
        for(Contact c : con){
            // 仅处理符合记录类型且有指定关联关系的Contact
            if(c.RecordTypeId == targetRecordTypeId && contactToAcrMap.containsKey(c.Id)){
                if(String.isBlank(c.MailingStreet)){
                    c.MailingStreet.addError(label.Messer_US_Contact_Street + ' on a ' + label.messer_US_Pricing_Letter + ' Contact cannot be null.');
                }
                if(String.isBlank(c.MailingCity)){
                    c.MailingCity.addError(label.Messer_US_Contact_City + ' on a ' + label.messer_US_Pricing_Letter + ' Contact cannot be null.');
                }
                if(String.isBlank(c.MailingState)){
                    c.MailingState.addError(label.Messer_US_Contact_State + ' on a ' + label.messer_US_Pricing_Letter + ' Contact cannot be null.');
                }
                if(String.isBlank(c.MailingPostalCode)){
                    c.MailingPostalCode.addError(label.Messer_US_Contact_Postal_Code + ' on a ' + label.messer_US_Pricing_Letter + ' Contact cannot be null.');
                }
                if(String.isBlank(c.MailingCountry)){
                    c.MailingCountry.addError(label.Messer_US_Contact_Country + ' on a ' + label.messer_US_Pricing_Letter + ' Contact cannot be null.');
                }
                if(String.isBlank(c.Email)){
                    c.Email.addError(label.Messer_US_Contact_Email + ' on a ' + label.messer_US_Pricing_Letter + ' Contact cannot be null.');
                }
                if(c.Active_Contact__c == false){
                    c.Active_Contact__c.addError('A Contact with an active ' + label.messer_US_Pricing_Letter + ' relationship cannot be made inactive.');
                }
            }
        }
    }//end isPricingLetter method
}//End Class

修复后的测试类代码(添加断言)

以updateStreetWithPricingLettersRelationship为例,其他测试方法同理修改:

static testmethod void updateStreetWithPricingLettersRelationship(){
    Contact c = getContact();
    Account a = getAccount();
    
    accountcontactrelation acr = new accountcontactrelation(accountid=a.id,contactid = c.id,Roles = label.messer_US_Pricing_Letter,isActive=true);
    insert acr;     

    Test.startTest();
    c.mailingstreet='';
    Boolean errorThrown = false;
    try{
        update c;
    }catch(DMLException e){
        errorThrown = true;
        // 验证错误信息是否匹配
        System.assert(e.getMessage().contains(label.Messer_US_Contact_Street + ' on a ' + label.messer_US_Pricing_Letter + ' Contact cannot be null.'));
    }
    Test.stopTest();
    // 验证错误确实被触发
    System.assert(errorThrown, 'Expected DML error when updating empty MailingStreet for Pricing Letter Contact');
}

额外优化点

  • 避免在循环中进行SOQL查询,改用集合和Map来批量处理,符合Salesforce的 governor limits 要求。
  • 使用String.isBlank()替代==null,同时校验空字符串的情况。
  • 测试类中使用Test.startTest()和Test.stopTest()分隔测试逻辑,确保触发器和处理器的代码在测试上下文正确执行。

内容的提问来源于stack exchange,提问作者RobIsNotATechGuy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 16:46:09