How to create Duplicate/Matching Rules through Apex code for an Apex test class

apexduplicate-managementsoqlunit-test

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();
                }
            }

Best Answer

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
Related Topic