5

As I implement Andrew Fawcett's, Apex Common library referring to the fflib-apex-common-samplecode when necessary, I note that his ObjectService classes (i.e. OpportunitiesService.cls) are really more of an Application Service Provider (ASP), in that they have a service() that instantiate's the proper Application Service Interface for which all their methods will then be invoked from.

OpportunitiesServiceASP.cls //The Application Service Provider

private static IOpportunitiesService service() {
    return (IOpportunitiesService) Application.Service.newInstance(IOpportunitiesService.class);
}

public static List<Opportunities> getOpportunities(Set<Id> ids){return service().getOpportunities(ids);}

From what I can see, the ASP is a gate keeper to the Service implementation itself, providing access to the Application Service so that each unit within the service doesn't have to instantiate the application with the same line...

Application.Service.newInstance(IOpportunitiesService.class);

OpportunitiesService.cls //The implementation class

public class OpportunitiesService implements IOpportunitiesService {
    public List<Opportunity> getOpportunities(Id id){
        // Use a selector, get opportunities and return list as expected
    }
}

The question I have, is what value does this abstraction provide at all? Given the overhead, since no business logic is actually stored in the methods of this class, would it make sense for unit testing to test the actual service unit through this ASP, rather than treating each method in it as it's own unit?

Therefore I would see it fit to use the ASP as the means to overload the method (if is the access point, why not overload the access methods and have them translate the different parameters to a single unit within the Service class. And then I would test with something like:

@IsTest
private static void AccountsService_getAccounts() {
    //! Given
    /* Create mocks */
    fflib_ApexMocks mocks = new fflib_ApexMocks();
    IOpportunitiesSelector mockSelector = (IOpportunitiesSelector)mocks.mock(OpportunitiesSelector.class);

    /* Configure the application to use mocks */
    Application.Selector.setMock(IOpportunitiesSelector.class, mockSelector);

    /* Generate a generic account id */
    Id id = fflib_IDGenerator.generate(Opportunity.SObjectType);

    mocks.startStubbing();
    List<Opportunity> oppList = new List<Opportunity> {
        new Account(
            Id = id,
            Name = 'Test Opportunity'
        ) 
    };

    mocks.when(mockSelector.selectById(id)).thenReturn(oppList);
    mocks.stopStubbing();

    //! When
    OpportunitiesServiceASP.getOpportunties(new Set<String>{id});

    //! Then
    /* Verify services were called */
    ( (OpportunitiesSelector) mocks.verify(mockSelector, 1) ).selectById( id );

}

Does this understanding seem appropriate? Also if I'm allowing the ASP to overload the inputs, would it be scope creep to also allow it to provide different return value methods that invoke the same service? Also if I went forward with this, I would assume the Service unit is the only thing I'm truly testing against, the ASP methods would just be covered through integration testing for code coverage.

Lastly, when referring to a service from the Domain layer, would it be best practice to call the ASP (as I've coined it here) or the service directly? I assume if the ASP purpose is to control access then even internally the other layers should call services through the ASP.

Thank you for any and all input on this!

3

The main benefit to this pattern is Dependency Injection, which has many benefits which you could spend entire weeks reading up on. I won't speak to your other questions, but this aspect I am familiar with.

Some of the benefits are:

  • it facilitates mocking for easier unit testing
  • it allows you to build a more loosely coupled codebase

As for the first, consider your example. If you want your service to query based on those Ids, you must insert your records to test any code which depends on that method. Except for the edge case where you expect an empty return value, of course. With the dependency injection in place, however, you can bypass this requirement and test against records which have not been inserted:

class MockOpportunityService implements IOpportunityService
{
    List<Opportunity> getRecords(Set<Id> ids)
    {
        return new List<Opportunity> { new Opportunity(); }
    }
}

Obviously you can get more sophisticated with the return value. Below is a slightly different model than used in fflib, but I believe it's the same basic idea.

public with sharing class OpportunityService implements IOpportunityService
{
    static IOpportunityService instance = new OpportunityService();
    @TestVisible static void setMock(IOpportunityService mock) { instance = mock; }
    public static IOpportunityService getInstance() { return instance; }

    public List<Opportunity> getRecords(Set<Id> ids)
    {
        return [SELECT Name FROM Opportunity WHERE Id IN :ids];
    }
}

Now if you want to test a method which depends on getRecords, you can do so without inserting the records.

public static void someMethod()
{
    for (Opportunity record : OpportunityService.getInstance().getRecords())
    {
        // do stuff
    }
}

static testmethod void testSomeMethod()
{
    OpportunityService.setMock(new MockOpportunityService());
    // no need to insert anything

    Test.startTest();
        MyClass.someMethod();
    Test.stopTest();

    // some assertions
}

Might not seem like a huge benefit with Opportunity data, but this approach is vitally important if you want to rigorously test any code dealing with a Custom Metadata Type.

| improve this answer | |
  • From what I understand, there very fact that there is an OpportunityService that implements the IOpportunityService interface, allows the dependency injection for mocking. The ASP (as I refer to it), just provides an additional layer between the actual service implementation and the application itself. Going to update question to clarify my thought more... – Xtremefaith Dec 20 '17 at 22:40
  • @Xtremefaith The use of an interface is only part of what allows mocking. Providing the service instance is what allows you to actually inject a different implementation of that interface. – Adrian Larson Dec 20 '17 at 22:42
  • Here is a perfect example of someone doing exactly as I am but without the ASP class, instead they just create a new instance for every unit within their implementation: quirkyapex.com/2017/11/30/apexmocks-argument-capture – Xtremefaith Dec 20 '17 at 23:04

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.