function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Jason GrossJason Gross 

React to duplicate value error within trigger

I have a simple apex method that generates a short ID and a trigger that attaches that ID to a custom text field on my Account object. 

In the configuration for this custom field, I specified that this field should be unique. 

There is a very slight chance that the method that creates my ID will create a duplicate. So, I'm looking for a way that my trigger can simply try again when Salesforce detects that the ID I just tried to put into my record is a duplicate. EventBus.RetryableException (https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_subscribe_apex_refire.htm) seems like the right path to take, but I'm having trouble either getting it to work properly or creating a test that can validate whether it is working. 

Method: 
 

public class GeneratePlanID {
    public static String generateRandomID(Integer len) {
        final String chars = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
        String randStr = '';
        while (randStr.length() < len) {
           Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), chars.length());
           randStr += chars.substring(idx, idx+1);
        }
        return randStr;
    }
}
Trigger:
trigger PlanIDTrigger on Account (before insert, before update) {
    try {
        for (Account a : Trigger.New) {
            if (Trigger.isInsert || a.PlanID__c == null) {
                String newID = GeneratePlanID.generateRandomID(4);
                a.PlanID__c = newID;
            }
        }
    } catch (Exception e) {
        if (EventBus.TriggerContext.currentContext().retries < 3) {
            throw new EventBus.RetryableException('Exception found retrying plan ID assignment', e);
        } else {
            System.debug('PlanID assignment reached max retries');
        }
    }
}
Not super helpful tests right now: 
@isTest
public class TestPlanIDTrigger {
    @isTest static void TestCreateNewAccount() {
        Account acct = new Account(Name='Test Account');
        
        Test.startTest();
        Database.SaveResult result = Database.insert(acct, false);
        Test.stopTest();
        
        System.assert(result.isSuccess());
        
        Account resultPlanID = [SELECT PlanID__c FROM Account WHERE ID = :result.getId()];
        System.assert(resultPlanID != null);
    }
    
    @isTest static void TestDuplicateAccountPlanID() {        
        
        Test.startTest();
        Account[] accts = new List<Account>{
            new Account(Name='Test Account 1', PlanID__c='ABCD'),
            new Account(Name='Test Account 2', PlanID__c='ABCD')
        };
        Database.SaveResult[] result = Database.insert(accts, false);
        Test.stopTest();
        
        for (Database.SaveResult sr : result) {            
            if (sr.isSuccess()) {
                System.debug('Successful insert of PlanID');
                System.assert(sr.isSuccess());
            } else {
                for (Database.Error err : sr.getErrors()) {
                    System.debug('Failed insert of PlanID');
                    System.debug(err.getStatusCode() + ': ' + err.getMessage());
                }
                System.assert(!sr.isSuccess());
                System.assert(sr.getErrors().size() > 0);
            }
        }        
    }
}

 
Daniel GrabowskiDaniel Grabowski
Hi Jason,

It sounds like you would just catch the error (I'm not sure what kind of exception... maybe DMLException?) and regenerate the random key inside the catch. 

Since the odds of two strings being equal is incredibly small, you may want to consider a Test.IsRunningTest scenario and/or a @TestVisible variable you can set from the test.

Good luck, 
Daniel