2

I recently wrote an API to implement Account/Contact Duplicate/Matching rules for a connected app. It all works fine.

It does the same thing that Salesforce does for these rules. It pretty much follows this example code: https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Datacloud_DuplicateResult.htm

I'm now trying to write a test class for this but I'm hitting a roadblock.

I need a way to do one of two things:

  1. Create Duplicate and Matching rules through Apex code within the test. I would create them, run the test, then delete them.

  2. Turn Duplicate and Matching rules on and off through Apex code within the test. Same general idea but I could create them beforehand in our testing org.

As far as I can tell there is no way to do this. Am I missing something here?

If this isn't possible then how do I get test coverage on my class. It will only get full code coverage if the call to Database.Insert actually fails with DuplicateErrors.

Edit: I should add that having rules always on and bypassing them with DMLHeader for testing is not an option.

Edit2: This is the code I am trying to test:

Database.SaveResult saveResult = Database.insert(contact, false);

        if (!saveResult.isSuccess()) {
            for (Database.Error error : saveResult.getErrors()) {
                // If there are duplicates, an error occurs
                // Process only duplicates and not other errors 
                //   (e.g., validation errors)
                if (error instanceof Database.DuplicateError) {
                    // Handle the duplicate error by first casting it as a 
                    //   DuplicateError class
                    // This lets you use methods of that class 
                    //  (e.g., getDuplicateResult())
                    Database.DuplicateError duplicateError = 
                            (Database.DuplicateError)error;
                    Datacloud.DuplicateResult duplicateResult = 
                            duplicateError.getDuplicateResult();
                    
                    // Display duplicate error message as defined in the duplicate rule
                    ApexPages.Message errorMessage = new ApexPages.Message(
                            ApexPages.Severity.ERROR, 'Duplicate Error: ' + 
                            duplicateResult.getErrorMessage());
                    ApexPages.addMessage(errorMessage);
                    
                    // Get duplicate records
                    this.duplicateRecords = new List<sObject>();

                    // Return only match results of matching rules that 
                    //  find duplicate records
                    Datacloud.MatchResult[] matchResults = 
                            duplicateResult.getMatchResults();

                    // Just grab first match result (which contains the 
                    //   duplicate record found and other match info)
                    Datacloud.MatchResult matchResult = matchResults[0];

                    Datacloud.MatchRecord[] matchRecords = matchResult.getMatchRecords();

                    // Add matched record to the duplicate records variable
                    for (Datacloud.MatchRecord matchRecord : matchRecords) {
                        System.debug('MatchRecord: ' + matchRecord.getRecord());
                        this.duplicateRecords.add(matchRecord.getRecord());
                    }
                    this.hasDuplicateResult = !this.duplicateRecords.isEmpty();
                }
            }
3
  • Does the code you're testing actually rely on the duplicate rules being in place when it internally calls Database.insert()? If so, why wouldn't you want to leave them on all the time? Sep 9, 2021 at 17:10
  • 1
    I see your edit about having them always on not being an option. I'm just still not clear on why that isn't an option. Could you include the code you need to test in the question? Sep 9, 2021 at 17:13
  • I updated the question with the code. Our test org does not have duplicate rules defined. It runs thousands of test classes every time an automated build happens so I cannot define duplicate rules and have them on in the org at all times. The only way to get coverage is to have Database.Insert return a DuplicateError. So like I said I need a way to create or toggle on/off duplicate rules from my test class
    – billg118
    Sep 9, 2021 at 17:23

1 Answer 1

3

Duplicate Rules and Matching Rules cannot be created or modified with Apex (not in a normal context, and not in a test context).

In situations where you need to test something that you don't have direct control over, or if it's somewhere between hard and impossible to set up a situation where thing X happens, it's often helpful to add a layer of abstraction.

For code that relies on something like Custom Metadata Types, you'd introduce a proxy class that you would use instead of calling My_Custom_MDType__mdt.getInstance(). To make it useful in tests, you would want that proxy class to provide a way to set (and use) specific test data.

Something like

public class MyMDTProxy{
    private Map<String, MyMetadataType__mdt> mdtCache;

    @testVisible
    private void setMDTCache(Map<String, MyMetadataType__mdt> testData){
        mdtCache = testData;
    }

    public MyMetadataType__mdt getInstance(String developerName){
        if(!mdtCache.containsKey(developerName)){
            MyMetadataType__mdt mdtRec = MyMetadataType__mdt.getInstance(developerName);
           
            mdtCache.put(mdtRec.DeveloperName, mdtRec);
        }

        return mdtCache.get(DeveloperName);
    }
}

You can create instances of CustomMetadataTypes in memory (we just can't perform DML on them), so it's simple to create an appropriate instance in a test and call setMDTCache().

In your particular situation, you should consider adding a layer of abstraction to your DML calls. Your concern here isn't really that your duplicate/matching rules work (that's, at the very lowest level, a concern for an integration test.) It's also a standard feature provided by Salesforce, so you should trust that they are testing it (and there are no issues).

Your concern here is what happens in your code when you encounter a Database.SaveResult with an error of type Database.DuplicateError. So you should create a proxy class that can return such data on demand.

Unfortunately, Database.SaveResult, Database.Error, Database.DuplicateError and the like are all non-constructable and non-extendable. So in order to test this, you'll need to:

  • Write a proxy class for the DML operation
  • Write a proxy class for Database.SaveResult
  • Write a proxy class for Database.Error
  • Write a proxy class for Database.DuplicateError
  • Write a proxy class for Datacloud.DuplicateResult
  • Write a proxy class for Datacloud.MatchResult
  • Write a proxy class for Datacloud.MatchRecord
1
  • Very close to what I was going to write up.
    – Adrian Larson
    Sep 9, 2021 at 18:02

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.