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
问题分析与修复方案
核心问题
- SOQL查询的是数据库旧记录而非触发器中的新记录:在before update触发器中,Trigger.new保存的是修改后的Contact记录,但SOQL查询的是数据库中的原始记录(修改前的状态)。比如测试中把mailingstreet设为空,SOQL查出来的还是原来有值的记录,自然不会触发非空校验的addError。
- 对查询出的记录添加错误无效:addError必须调用在Trigger上下文的记录(即Trigger.new中的实例)上,才能阻止DML操作并抛出错误。对SOQL查询返回的Contact实例调用addError,这些实例不属于当前触发器上下文,所以不会生效。
- 硬编码RecordTypeId:直接使用ID值会导致代码在不同环境(沙盒/生产)中失效,应该通过记录类型名称动态获取ID。
- 测试类缺乏断言:仅靠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
相关产品推荐
相关产品推荐

