I have written a trigger that will aggregate into a list all instances of an opportunity picklist on any contact with a (opportunity) contact role relating it to an opportunity. I.E. Contacts A and B have OpportunityContactRoles with opportunity 1, 2, and 3. Opportunity 1.program_type = "hello", Opportunity 2.program_type = "hello", Opportunity 3.program_type = "goodbye." After an insert or update, Contacts A and B should have a field that reads '["hello","goodbye"]'.

I've tried to write a test class for this trigger, but even though I insert the new data and update the opportunity, the field is not populated. However, when I take the same actions through an execute anonymous, the trigger works perfectly. Could someone help me ascertain what I am doing incorrectly?

Trigger code:

trigger ProgType on Opportunity (after insert, after update, after delete, after undelete) {
    //Create contact list
    set<ID> contactIds = new set<ID>();
    //Create opportunity list
    set<ID> oppIds = new set<ID>();
    //Add opportunity IDs to opportunity list for all opportunities that are triggering
    if (Trigger.new != null) {
        for (Opportunity record: trigger.new) {
            if(record.recordtypeid == '012a00000018Esx' || record.recordtypeid == '012300000006GiS' || record.RecordTypeId == '012300000006GiI') {
    if (trigger.old != null) {
        for (Opportunity record: trigger.old) {
            if (record.recordtypeid == '012a00000018Esx' || record.recordtypeid == '012300000006GiS' || record.RecordTypeId == '012300000006GiI') {
    //below is the code to populate the all program types field on contacts related to this opportunity
    //remove any null values from the Opportunity list   
    //Add all contacts to the Contact list that have a contact role related to an opportunity in the Opportunity list
    for (opportunitycontactrole o: [select contactid from opportunitycontactrole where opportunityid in :oppIds]) {
    //remove any null values from the Contact List
    //create a new list of opportunities
    list<id> newOppIds = new list<id>();
    //populate the new list with all opportunities related to Contacts in the contact list as investor, joint investor, trustee, or account representative
    for (opportunitycontactrole o: [select opportunityid, opportunity.stagename from opportunitycontactrole where contactid in :contactIds and role in ('Investor','Joint Investor','Trustee','Account Representative')]) {
        if (o.Opportunity.stagename=='converted') {
    //create blank map of contact id and set of program types
    map<Id, Set<String>> programs = new Map<Id, Set<String>>();
    //populate map with contact id and all related program types
    for (Opportunitycontactrole record: [SELECT contactid, opportunity.program_type__c FROM Opportunitycontactrole WHERE opportunityid in :newOppIds]) {
        if (programs.containsKey(record.contactid)) {
        } else {
            programs.put(record.contactid, new Set<String> { record.opportunity.program_type__c });
    //create blank list of contacts to be updated
    Contact[] contacts = new Contact[0];
    //populate list of contacts to be updated
    for (Id contactID: contactIds) {
        contacts.add(new Contact(ID=contactId, All_Program_Types__c = programs.containsKey(contactId)?JSON.serialize(programs.get(contactId)):null));
    update contacts;

Test Class Code:

public with sharing class TestProgType {

    @isTest static void testProgTypeTrigger(){

        //Test data setup
        //Create contact, opportunity, and opportunity contact role
        contact testcontact = new contact (firstname = 'test', lastname = 'testing', recordtypeid = '01230000000p4Eb');
        opportunity testopp = new opportunity (name='testing',stagename='converted',closedate=date.today(),recordtypeid = '012a00000018Esx');
        testOpp.program_type__c = 'OTO: Twin Cities';
        insert testcontact;
        insert testOpp;
        opportunitycontactrole testRole = new opportunitycontactrole (contactid = testcontact.id, opportunityid = testOpp.id, role = 'Investor',isprimary = TRUE);
        insert testrole;

        system.assert(testcontact.All_Program_Types__c == null);

        //perform test
        testOpp.program_type__c = 'OTO: Denver';
        update testOpp;

        //Verify that testcontact now has text

        system.assert(testcontact.All_Program_Types__c != null);

1 Answer 1


Records updated through a DML statement don't automatically show any changes to the caller (except for an ID field being updated on inserts). You must first query the data back from the database after performing a DML statement before comparing the value:

testContact = [SELECT All_Program_Types__c FROM Contact WHERE Id = :testContact.Id];
System.assertNotEquals(null, testContact.All_Program_Types__c);
  • If I may ask a follow up question: After this test class, I have 96% coverage with the exception of line 44: "programs.get(record.contactid).add(record.opportunity.program_type__c);" Would I need to insert another opportunity belonging to the same contact in order to test that part of the loop? -------UPDATE: It worked! :) 100% coverage Jul 15, 2015 at 20:04
  • @krewllobster Good. I was going to say that there's a way you can optimize the code to reduce the number of opportunities to 1 (for code coverage), but I found out over the last month or so that it is a pretty severe CPU hit (40-50ms per record), so this would indeed be ideal.
    – sfdcfox
    Jul 15, 2015 at 20:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .