This is a simple implementation of dependency injection within SFDC.
The main goal is to abstract away any inlined instructions that are native to apex
or that are implemented as static methods instead of instance. The main benefit
of abstracting these things away is that it will allow you to mock them
when testing. The main offenders of things being inlined are DML actions like
insert
, update
, or delete
or any calls to the System.Database
class
and SOQL/SOSL queries.
Repository class:
This class is designed to to do any crud actions or anything that you would
do via the System.Database
class. Its design is simple in that it just
forwards to the inlined crud/database call you would want to use. You can
take this class a step further and catch all exceptions, log them, and rethrow
or provide common functionality like making sure the arguments aren't null or
empty.
Selector classes: These classes provide consistent queries to access your database. You would have one per entity type. Abstracting these provides several benefits: it allows them to be more thoroughly tested, reusable, and can be mocked when testing classes that depend on them. Andy Fawcett has good blogs about the selector layer and goes further in depth about the benefits.
Dependency injection: Dependency injection can occur in 3 areas: Triggers, Visualforce controllers, and Aura controllers. I've provided a simple pattern of constructor based dependency injection that passes these dependencies down to the classes that need them. Dependencies provide a public INSTANCE variable that makes creating and using them easier.
In triggers, they are passed directly to whatever abstraction you are using to handle your trigger invocations.
In visualforce pages SFDC needs an empty constructor to create the controller so you should provide a second constructor that takes the dependencies the controller needs. You would then call this constructor with your dependency instances.
In aura controllers you auraenabled methods must be static, this requirement forces us to forward any functionality to an INSTANCE of our self.
Bringing it all together: So what is the point of doing all this work? Mocking! After we've implemented these patterns we can use a mocking library like financial force's apex mocks to greatly speed up our development time and deployment time!
Caveats and where this pattern fails: This pattern will fail once your codebase begins to grow. The main problem areas I see are within the selector classes as the amount of queries you have grows and the different number of fields we want to return changes. In this situtation I would then implement something like financial force's Selector layer from its apex commons library.
It will also fail as your amount of required fields and validation rules change throughout the life of your application. This is because your validation rules/required fields will never actually be fired as all IO is being mocked away and the database never actually contacted. In other languages this is solved by validation happening before any of your business logic begins to execute. You could implement this by providing a validator that fires before insert and validates that all required fields are present etc. This will add some development time, but I think the time saved writing unit tests and avoiding 5 hour long deployments is worth it. Another solution is to write smaller integration tests for specific functionality you need so you can be sure it doesn't break later. (i.e. if you are inserting opportunities with null accountId's write a test that does that, so you can be sure it will succeed even if you introduce validations later) This will still be leagues faster as you are only inserting 1 record 1 time instead of hundreds over the course of your tests and triggers.
The last caveat is that you will have certain parts of code that isn't covered, mainly the injection sites. It is important that the injection sites are simple, without complex logic and should mainly only contain construction as these are validated at compile time.
@jlyon87 Yes basically its saying when this method is called with X parameter then return Y value. ApexMocks is based around https://github.com/mockito/mockito so maybe looking at tutorials or docs for that could help.