fflib-apex-common-samplecode icon indicating copy to clipboard operation
fflib-apex-common-samplecode copied to clipboard

Hello World Tutorial for APEX Mocks

Open mhaseebkhan opened this issue 8 years ago • 10 comments

I am working on a project in which we need to test an APEX REST Service in such a way that no actual data is created in Salesforce during the testcases execution. I am new to APEX Development.

Searching on the web took me to fflib-apex-mocks library which seems to serve the purpose of avoiding the usage of actual test data inserted in the database for testcases execution. I have installed fflib-apex-mocks and fflib-apex-common in my sandbox environment.

So far, I am not able to find out a step-by-step guide as what I need to do if I want to test my existing Salesforce Objects at a unit level and how can I test my APEX Rest Service? From looking at the fflib-apex-common-samplecode it seems that I need to define classes for my Salesforce objects, I am not sure if I am getting this right.

At a higher-level, below are my questions at a higher-level:

  • I have a Student__c Object already defined in Salesforce, it has some triggers for insert and update which has checks for various fields and I want these triggers to fire when I try an insert or update for the mock object. I want to develop testcases for that, what do I need to do? How do I start?
  • I have an APEX Rest Service which gets a collection of Teacher__c objects converted to JSON based on the id of a Student__c object provided to it as a query parameter. I have previously developed testcases for APEX Rest Service but that didn't involved actual objects usage but my current scenario is different as it involves usage of actual objects.

Can someone please help me out in getting started?

mhaseebkhan avatar Apr 04 '16 19:04 mhaseebkhan

So ApexMocks and Apex Enterprise Patterns have some defined points where mocks can be injected to allow you to test your class code in isolation without needing to setup things dependent classes require. I cover this in my Dreamfroce 2015 talk, recorded here. There is also a two part blog series, here and here.

In your case I'd expect you to have the following...

  • An Apex Domain class (for your trigger code)
  • An Apex Service class (for your Teacher query service code, caller agnostic)
  • An Apex Rest class (defines your REST API and delegates to your Apex Service class code).

Traditional Apex Tests are Integration Tests

You will need at least one integration unit test for your trigger and your REST API entry points. These test the full stack, needed for at least for the one line Trigger coverage if nothing else. Basically these are your traditional apex test that inserts some objects, asserts the domain / trigger code did its thing. And also one that inserts some records, calls the Apex REST class method (emulating the URL parameters) and asserts it returns the correct records.

Ok, but lets get writing some more true Unit Tests

So what we want to do is write some more varied and more finely targeted unit test methods. In your case the following and mocking accordingly...

  • Unit tests for the Apex Domain class code only (mocking any class it calls, such as a Selector)
  • Unit tests for the Apex Service class code only (mocking any class it calls perhaps a Selector call to query Students)
  • Unit tests for the Apex Rest class code only (mocking the Service call)

Enabling Dependency Injection

The blogs above show how to generate the mock classes for Selectors, Domain and Service classes and how to use them in your code (via the Application factory class) to facilitate runtime mocking. The blogs also show how to inject mock versions of each according to what test from above your writing.

For example when unit testing your Apex Rest class code, you would want to mock the Service layer calls. This example while its a controller class test (imagine its your rest apex class) shows how this works.

Other thoughts...

The blog links above should give a good start in terms of how to setup your classes and interfaces. I do think though you raise a good point and something i'd like to keep this issue open to represent the need for a more step by step list for sure.

afawcett avatar Apr 13 '16 21:04 afawcett

Thanks, @afawcett for your input. I posted similar question on StackExchange which was answered by @frup42 previously.

Initially, my code didn't had any layers but now I have added a Service Layer which is responsible for executing DML, however, I am facing problem while Mocking it.

As a sample, I have provided a snippet below to explain the scenario.

// Service class
global with sharing class TeacherService {
  global static Teacher[] getAllTeachers() {
    Teacher[] teachers = [Select Id, Name From Teacher];
    return teachers;
  }
}

// Testing code
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_ISObjectUnitOfWork uowMock = new fflib_SObjectMocks.SObjectUnitOfWork(mocks);
TeacherService serviceMock = new Mocks.TeacherService(mocks);

// Below is the error on line 3 of the testing code above.
// Invalid type: Mocks.TeacherService

Can you please have a look and suggest if I am doing something wrong? I have tried to follow fflib-apex-common-samplecode to the best of my understanding.

Looking forward to hear from you on this soon.

mhaseebkhan avatar Apr 14 '16 02:04 mhaseebkhan

Excellent, i've up-voted both question and answer! :+1:

Your service is missing the Apex interface and the implementation of it that allows ApexMocks to do its thing. The missing class is created by the Apex Mocks Generator. If you take a look at Part 1 of the blog i referenced above there is an example of how to setup, register and utilise your service code from your Apex REST class method, to make this work. Part 2 talk about configuring and running the Apex Mocks Generator.

If your testing your Apex Rest Class code, the only thing you need to mock is the Service. You don't need to mock the Unit Of Work, as that is not a dependency of your Apex Rest Class code. However once you get to writing your test code for the Service itself you will want to mock the Unit of Work (assuming your using the Unit Of Work, your example above is just a query) and/or Selector dependencies (again only if you have any).

You will need....

  • ITeacherService.cls (defines your service methods)
  • TeacherService.cls (has service methods that instiantiate an implementation of ITeacherService)
  • TeacherServiceImpl.cls (implements the interface, your service code actually goes here)
  • TeacherResource.cls (calls your TeacherService methods)
  • Application.cls (with the Service Factory defined in it, this class helps get an instance of your service and helps mock it)

Again the blogs I've referenced above show how to setup the above classes. And critically run the Apex Mocks Generator tool, to generate the Mocks.TeacherService mock class. Its also worth noting that typically you would extract your SOQL code into a Selector, but lets leave that for now, once you have this up and running you'll see how to do it for Selector and Domain layers.

afawcett avatar Apr 14 '16 07:04 afawcett

ITeachersService.cls

public interface ITeachersService
{
    Teacher__c[] getAllTeachers();
}

TeachersService.cls

global with sharing class TeachersService
{
    global static Teacher__c[] getAllTeachers()
    {
        return service().getAllTeachers();
    }

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

TeachersServiceImpl.cls

public class TeachersServiceImpl
    implements ITeachersService
{
    public Teacher__c[] getAllTeachers() {
        Teacher__c[] teachers = [Select Id, Name From Teacher__c];
        return teachers;
    }
}

Application.cls*

public class Application
{
    // Configure and create the ServiceFactory for this Application
    public static final fflib_Application.ServiceFactory Service =
        new fflib_Application.ServiceFactory(
            new Map<Type, Type> {
                    ITeachersService.class => TeachersServiceImpl.class });
}

TeachersResource.cls

@RestResource(urlMapping='/Teachers/*')
global class TeachersResource 
{
    @HttpGet
    global static Teacher__c[] getAllTeachers() 
    {
        return TeachersService.getAllTeachers();
    }
}

TeachersResoruceTest.cls

@IsTest 
private class TeachersResourceTest 
{
    private void callingRestMethodCallServiceWhenInvoked()
    {
        fflib_ApexMocks mocks = new fflib_ApexMocks();

       // Given
       mocks.startStubbing();
       Mocks.TeachersService mockService = new Mocks.TeachersService(mocks);
       mocks.when(mockService.getAllTeachers()).thenReturn(
            new List<Teacher__c> { new Teacher__c( Name = 'Fred')}); // Mock service response
       mocks.stopStubbing();

       // When 
       List<Teacher__c> teachers = TeachersResource.getAllTeachers(); // (HTTP GET on /Teachers)

       // Then 
       (IOpportunitiesService) mocks.verify(mockService, 1)).getAllTeachers(); // Service called?
       System.assertEquals('Fred', teachers[0].Name);  // Mocked Service response returned?
    }
}

Some Notes:

  • The above has not been tested in an org, just typed in here
  • I don't typically recommend exposing SObject's from Apex API's, DTO's make a better contract as its clear what fields are populated from the DTO (Apex Class) definition, its just a preference though in this case.
  • The above example makes it look likely you could use the TeachersService as your REST service, this is due to the simple example used, in reality the method signatures may not match.
  • When you have more complex REST API and Service methods, with parameters, you'll be unit testing the behaviour of the Apex REST class code, such as parsing the URL and/or parameters and marhsalling back out results.
  • Unless your also providing an API to Apex developers you don't need your Service to be global
  • You should not do an unfiltered SOQL query
  • You would typically do your SOQL query from a Selector class

afawcett avatar Apr 14 '16 08:04 afawcett

Thanks for your input and directions, @afawcett. It has been certainly very helpful. The file extension I have is apxc and not cls, but, I am not sure if that can cause a problem.

Also, please provide your input on the following:

  • Is it necessary to have Application.apxc? Can't I directly instantiate fflib_Application.ServiceFactory in the TeachersService.service() method? It might not be a best practice, however, I am asking for the sake of my learning. In my opinion, below code can be used:

TeachersService.apxc

global with sharing class TeachersService
{
    global static Teacher__c[] getAllTeachers() {
        return service().getAllTeachers();
    }

    private static ITeachersService service() {
        fflib_Application.ServiceFactory Service = new fflib_Application.ServiceFactory(
            new Map<Type, Type> {
                ITeachersService.class => TeachersServiceImpl.class
            }
        );
        return (ITeachersService) Service.newInstance(TeachersServiceImpl.class);
    }
}
  • The issue I am facing is that the both the actual and mock methods gets called from my REST service. Below is my Mocks class, the test class is the same as you mentioned in your reply. I am not sure what I am doing wrong.

Mocks.apxc

@isTest
public class Mocks {
    public class TeachersService {
        private fflib_ApexMocks mocks;

        public TeachersService(fflib_ApexMocks mocks) {
            this.mocks = mocks;
        }

        public Teacher__c[] getAllTeachers() {
            return (Teacher__c[]) mocks.mockNonVoidMethod(this, 'getAllTeachers', new List<Type>(), new List<Object>());
        }
    }
}

Looking forward to your input.

mhaseebkhan avatar Apr 14 '16 21:04 mhaseebkhan

You need to have the ServiceFactory held at request scope, e.g. in a static some place, it does not need to be in Application class, this is just a naming convention. Basically it needs to be static, because you will have no way to register your Mock instance before invoking the code you want to test.

I'm not 100% sure what you mean by both the actual and mock methods gets called from my REST service tbh. Looking at this code above it should always use the none mock route as you have not provided anyway to register the mock implementation, though i've not seen your test code so cannot comment entirely on this. Can you share that?

afawcett avatar Apr 17 '16 09:04 afawcett

Thanks for the reply, @afawcett. Please find below the code.

ITeachersService.cls

public interface ITeachersService {
    Teacher__c[] getAllTeachers();
}

TeachersServiceImpl.cls

public class TeachersServiceImpl implements ITeachersService {
    public Teacher__c[] getAllTeachers() {
        Teacher__c[] teachers = [Select Id, Name From Teacher__c];
        return teachers;
    }
}

TeachersService.cls

global with sharing class TeachersService {
    global static Teacher__c[] getAllTeachers() {
        return service().getAllTeachers();
    }

    private static ITeachersService service() {
        fflib_Application.ServiceFactory Service = new fflib_Application.ServiceFactory(
                new Map<Type, Type>{
                        ITeachersService.class => TeachersServiceImpl.class
                }
        );
        return (ITeachersService) Service.newInstance(TeachersServiceImpl.class);
    }
}

TeachersResource.cls

@RestResource(URLMapping='/Teachers')
public with sharing class TeachersResource {
    @HttpGet
    global static Teacher[] getTeachers() {
        return TeachersService.getAllTeachers();

    }
}

TeachersResourceTest.cls

@IsTest
private class TeachersResourceTest {
    private void callingRestMethodCallServiceWhenInvoked() {
        fflib_ApexMocks mocks = new fflib_ApexMocks();

        // Given
        mocks.startStubbing();
        Mocks.TeachersService mockService = new Mocks.TeachersService(mocks);
        mocks.when(mockService.getAllTeachers()).thenReturn(
                new List<Teacher__c>{
                        new Teacher__c(Name = 'Fred')
                }
        );
        mocks.stopStubbing();

        // When
        List<Teacher__c> teachers = TeachersResource.getAllTeachers();

        // Then
        ((ITeachersService) mocks.verify(mockService, 1)).getAllTeachers();
        System.assertEquals('Fred', teachers[0].Name);
    }

    private class MockTeachersService implements TeachersService.ITeachersService {
        private fflib_ApexMocks mocks;

        public MockTeachersService(fflib_ApexMocks mocks) {
            this.mocks = mocks;
        }
    }
}

The APEX Mocks generator inserted the private class MockTeachersService implements TeachersService.ITeachersService piece in TeachersResourceTest.cls. I am not sure if the class is correct because its named as MockTeachersService but it should be named as TeachersService as thats what is used in TeachersResourceTest. Also MockTeachersService doesn't have a getAllTeachers method, so, I added it manually.

By both actual and mock methods I mean is that when I execute the testcase, the getAllTeachers method in MockTeachersService and TeachersService gets called. I checked that with putting some System.debug statements.

Kindly suggest what changes do I need to do.

mhaseebkhan avatar Apr 18 '16 21:04 mhaseebkhan

Ah sorry, my bad, the key thing is missing, setting the mock instance!

Application.Service.setMock(ITeachersService.class, mockService);

afawcett avatar Apr 24 '16 00:04 afawcett

public class Application { // Configure and create the ServiceFactory for this Application public static final fflib_Application.ServiceFactory Service = new fflib_Application.ServiceFactory( new Map<Type, Type> { ITeachersService.class => TeachersServiceImpl.class }); }

public interface ITeachersService { Teacher__c[] getAllTeachers(); }

global with sharing class TeachersService { global static Teacher__c[] getAllTeachers() { return service().getAllTeachers(); }

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

}

public class TeachersServiceImpl implements ITeachersService { public Teacher__c[] getAllTeachers() { Teacher__c[] teachers = [Select Id, Name From Teacher__c]; return teachers; } }

@RestResource(urlMapping='/Teachers/*') global class TeachersResource { @HttpGet global static Teacher__c[] getAllTeachers() { return TeachersService.getAllTeachers(); } }

@IsTest private class TeachersResourceTest { @istest private static void callingRestMethodCallServiceWhenInvoked() { fflib_ApexMocks mocks = new fflib_ApexMocks();

    // Given
    mocks.startStubbing();
    Mocks.TeachersService mockService = new Mocks.TeachersService(mocks);
    Application.Service.setMock(ITeachersService.class, mockService);
    mocks.when(mockService.getAllTeachers()).thenReturn(
        new List<Teacher__c>{
            new Teacher__c(Name = 'Fred')
                }
    );
    mocks.stopStubbing();
    
    // When
    List<Teacher__c> teachers = TeachersResource.getAllTeachers();
    
    // Then
    ((ITeachersService) mocks.verify(mockService, 1)).getAllTeachers();
    System.assertEquals('Fred', teachers[0].Name);
}

}

@isTest public class Mocks { public class TeachersService { private fflib_ApexMocks mocks;

    public TeachersService(fflib_ApexMocks mocks) {
        this.mocks = mocks;
    }

    public Teacher__c[] getAllTeachers() {
        return (Teacher__c[]) mocks.mockNonVoidMethod(this, 'getAllTeachers', new List<Type>(), new List<Object>());
    }
}

}

When I ran TeachersResourceTest, I got this error: System.TypeException: Invalid conversion from runtime type Mocks.TeachersService to ITeachersService. Can you please advise?

jinlii avatar May 06 '21 17:05 jinlii

Hi @jinlii

(1) Although it's evident that you're mocking the service implementation in the test, the test-sphere naming is a bit confusing. (2) The mock should implement the ITeachersService interface too.

@isTest
public class Mocks {
    public class TeachersService implements ITeachersService {
        ...
    }
}

stohn777 avatar May 07 '21 16:05 stohn777