1

Question: How can a unit test cover the throw exception part?

        List<abc__mdt> settings = [
            SELECT DeveloperName, Value__c
            FROM Setting__mdt
            WHERE DeveloperName = :name
        ];

        if (settings.isEmpty() || !settings[0].Value__c.isNumeric()) {
            String error = 'Custom error message';
            throw new ElisaOCServiceException(error);
        }
1
  • 1
    The approach we take is to use non-static methods, to make the class containing this code virtual, to separate out the actual custom metadata loading into a separate virtual method, to provide a test-specific version of this class (as an inner class in the unit tests) that overrides the virtual loading function and uses JSON.deserialize to provide dummy data, from JSON passed to the test version's constructur, and use this test version in all unit tests except one that uses the "real" class to ensure the "real" loading function works.
    – Phil W
    Commented Jun 18, 2020 at 10:04

1 Answer 1

4

Our approach is to make sure unit tests don't use actual data but always use test data. Since Apex cannot control custom metadata instances, and those on the org are accessible in unit tests you need to avoid actually querying this data. Thus:

  • Use non-static methods, so we can mock out or override behaviour in unit tests
  • Make the methods we need to mock/override virtual
    • Split out data loading into separate virtual methods to allow this to be mocked
  • Use in-test-class specializations to drive specific behaviours

So, for example, your main code might be like:

public with sharing virtual class Xyz {
    public void myMethod(String name) {
        List<Abc__mdt> settings = loadSettings(name);

        if (settings.isEmpty() || !settings[0].Value__c.isNumeric()) {
            String error = 'Custom error message';
            throw new ElisaOCServiceException(error);
        }
    }

    protected virtual List<Abc__mdt> loadSettings(String name) {
        return [
            SELECT DeveloperName, Value__c
                FROM Setting__mdt
                WHERE DeveloperName = :name
        ];
    }
}

Then in your unit tests do something like:

@IsTest
class XyzTest {
    class TestXyz extends Xyz {
        private String theJSON;

        public TestXyz(String theJSON) {
            this.theJSON = theJSON;
        }

        protected override List<Abc__mdt> loadSettings(String name) {
            if (String.isBlank(theJSON) || String.isBlank(name)) {
                return null;
            } else {
                return (List<Abc__mdt>) JSON.deserialize(theJSON, List<Abc__mdt>.class);
            }
        }
    }

    @IsTest
    void testNoValidMDTsThrowsException() {
        Xyz testInstance = new TestXyz(null);

        try {
            testInstance.myMethod(null);

            System.assert(false, 'Should have had an exception');
        } catch (ElisaOCServiceException e) {
            // The expected situation
        }
    }

    ...
}

Note that if you use non-static methods you could also leverage the Apex mocking facilities, which avoids the need for virtuals, but still requires you to structure your code to make it testable. See mocking with the StubProvider for details.

1
  • 1
    +1 - or use fflib Selector class pattern and mock with ApexMocks - but basically, Dependency Injection as you have answered
    – cropredy
    Commented Jun 18, 2020 at 14:10

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