GithubHelp home page GithubHelp logo

gs-apex-mocking-framework's Introduction

Mocker Mocking Framework

Mocker is a mocking framework that helps you create mocks for APEX classes and interfaces. It is super easy to use and helpful for unit testing.

How To Create Unit Tests For Your Application

Unit testing is a methodology used to test individual units of source code in an application. In OOP those units are classes and their public methods.

Classes can have dependencies; in other words, classes can call other classes. To create unit tests, you must replace these dependencies with some kind of code you have control over. Those replacement classes are commonly called mocks.

         Runtime dependencies                          Test dependencies
             -------------                               -------------
            |   MyClass   |                             |   MyClass   |
             -------------                               -------------
              /         \               ====>             /         \
             /           \                               /           \
   -------------        -------------          -------------        -------------
  | Dependency1 |      | Dependency2 |        |    Mock1    |      |    Mock2    |
   -------------        -------------          -------------        -------------

Preparing Your Classes For Unit Testing

To use the mocked classes created by the Mocker Framework is necessary to adapt your target classes to receive these mock instances. There are several ways to do it. Here are some suggestions.

1 - Passing Dependencies Through The Class Constructor

The tested class dependencies will be received through the class constructor. We usually use this approach with some Dependency Injection framework.

public class MyService {

    private IMySelector mySelector;

    public MyService(IMySelector mySelector) {
        this.mySelector = mySelector;
    }

    public void doSomething() {
        // ....
        List<Account> account = mySelector.getAccounts();
        // ....
    }
}

2 - Using @TestVisible Private Fields

The class dependencies will be created using private fields. Those dependencies can be replaced by mock instances during the unit testing.

public class MyService {

    @TestVisible
    private IMySelector mySelector = new MySelector();

    public void doSomething() {
        // ....
        List<Account> account = mySelector.getAccounts();
        // ....
    }
}

3 - Using An IOC Container

The class dependencies will be received through an IOC container. During the testing phase we can instruct the IOC container to return the mock instance instead of the real implementation.

public class MyService {

    public void doSomething() {
        // ....
        IMySelector mySelector = Container.instance.get(IMySelector.class);
        List<Account> account = mySelector.getAccounts();
        // ....
    }
}

Using The Mocker Framework To Mock Your Dependencies

Mocker Framework Phases

The Mocker Framework works in three phases: Stubbing, Executing (Replaying) and Asserting.

  • Stubbing phase: The Mocker Framework will record the mock actions and expected behaviours;
  • Executing phase: It will replay the recorded actions such as mocked return values or exceptions, and it will record any action executed over the mock instance;
  • Asserting phase: It will assert if the mock has executed the expected behaviours.

Mocking Methods Return Value

Given the following dependency class...

public class MyDependency {

    public Contact getContactByEmail(String email) {
        return [SELECT Id, NAME FROM Contact WHERE Email = :email LIMIT 1];
    }

    public void insertContact(Contact contact) {
        insert contact;
    }
}

Mocking The Method Return For Specific Parameter Values

You can mock getContactByEmail return value for specific email values...

@IsTest
public class MyTest {
    @IsTest
    static void myTestExample01() {
        Mocker mocker = Mocker.startStubbing();
        MyDependency myMock = (MyDependency) mocker.mock(MyDependency.class);

        // Mocking getContactByEmail return values
        mocker.when(myMock.getContactByEmail('[email protected]'))
            .thenReturn(new Contact(FirstName = 'Leonardo'));

        mocker.when(myMock.getContactByEmail('[email protected]'))
            .thenReturn(new Contact(FirstName = 'Berardino'));

        // Going to the execution phase
        mocker.stopStubbing();

        // Getting the mocked return values

        // It will return Leonardo contact
        Contact contact1 = myMock.getContactByEmail('[email protected]');
        // It will return Berardino contact
        Contact contact2 = myMock.getContactByEmail('[email protected]');
        // It will return null
        Contact contact3 = myMock.getContactByEmail('[email protected]');
    }
}

Mocking The Method Return For Any Parameter Values

Or you can mock getContactByEmail return value for any email values...

@IsTest
public class MyTest {
    @IsTest
    static void myTestExample01() {
        Mocker mocker = Mocker.startStubbing();
        MyDependency myMock = (MyDependency) mocker.mock(MyDependency.class);

        // Mocking getContactByEmail return values
        mocker.when(myMock.getContactByEmail('')) // It's not necessary to give an email
            .withAnyValues() // Because the parameter value will be ignored
            .thenReturn(new Contact(FirstName = 'Leonardo'));

        // Going to the execution phase
        mocker.stopStubbing();

        // Getting the mocked return values
        // It will return Leonardo contact
        Contact contact1 = myMock.getContactByEmail('[email protected]');
        // And again Leonardo contact
        Contact contact2 = myMock.getContactByEmail('[email protected]');
        // And again :)
        Contact contact3 = myMock.getContactByEmail('[email protected]');
    }
}

Combining The Mocking Method Return Strategies

Or you can mix both approaches...

@IsTest
public class MyTest {
    @IsTest
    static void myTestExample01() {
        Mocker mocker = Mocker.startStubbing();
        MyDependency myMock = (MyDependency) mocker.mock(MyDependency.class);

        // Mocking getContactByEmail return values
        // Mocking return for Leo email
        mocker.when(myMock.getContactByEmail('[email protected]'))
            .thenReturn(new Contact(FirstName = 'Leonardo'));

        // And mocking for any values
        mocker.when(myMock.getContactByEmail(''))
            .withAnyValues()
            .thenReturn(new Contact(FirstName = 'Contact for any values'));

        // Going to the execution phase
        mocker.stopStubbing();

        // Getting the mocked return values
        // It will return Leonardo contact
        Contact contact1 = myMock.getContactByEmail('[email protected]');
        // It will return Contact for any values
        Contact contact2 = myMock.getContactByEmail('[email protected]');
        // It will return Contact for any values
        Contact contact3 = myMock.getContactByEmail('[email protected]');
    }
}

Using Callable To Generate Dynamic Return Values

You can implement the System.Callable interface and use this class to generate dynamic values that will be returned by the mocked method.

The methodName parameter will contain the method name that was called, and the params parameter will contain the arguments passed to it during the method execution.

An example of a callable mock return:

public class GetContactCallableReturn implements Callable {

    public Object call(String methodName, Map<String, Object> params) {
        // Gets the email parameter.
        String email = (String) params.get('email');
        return new Contact(Email = email);
    }
}

You can pass your callable class instance on the .thenReturn method. It will instruct the Mocker Framework to ask your callable what the return value is.

    mocker.when(myMock.getContactByEmail('')) // It's not necessary to give an email
        .withAnyValues() // Because the parameter value will be ignored
        .thenReturn(new GetContactCallableReturn()); // Your callable mock return.

Asserting Captured Arguments

The Mocker Framework records all arguments passed to a mock method, and these data is accessible through the Mocker.MethodRecorder. It allows you to track and assert all data received by your mock class.

@IsTest
public class MyTest {
    @IsTest
    static void myTestExample01() {
        Mocker mocker = Mocker.startStubbing();
        MyDependency myMock = (MyDependency) mocker.mock(MyDependency.class);

        // Mocks getContactByEmail and gets the method recorder.
        Mocker.MethodRecorder getContactByEmailRec = mocker.when(myMock.getContactByEmail(''))
            .withAnyValues()
            .thenReturn(new Contact(FirstName = 'Contact Name'))
            .getMethodRecorder(); // Allows access to the method recorder.

        // Going to the execution phase
        mocker.stopStubbing();

        // Executes the first call with '[email protected]'
        Contact contact1 = myMock.getContactByEmail('[email protected]');
        // Executes the second call with '[email protected]'
        Contact contact2 = myMock.getContactByEmail('[email protected]');
        // Executes the third call with '[email protected]'
        Contact contact3 = myMock.getContactByEmail('[email protected]');

        // Asserts how many call the method getContactByEmail received
        System.assertEquals(3, getContactByEmailRec.getCallsCount());

        // Asserts the arguments received in each call getting
        // these arguments using the parameter name.

        // Asserts the first call.
        System.assertEquals(
            '[email protected]', getContactByEmailRec.getCallRecording(1).getArgument('email')
        );
        // Asserts the second call.
        System.assertEquals(
            '[email protected]', getContactByEmailRec.getCallRecording(2).getArgument('email')
        );
        // Asserts the third call.
        System.assertEquals(
            '[email protected]', getContactByEmailRec.getCallRecording(3).getArgument('email')
        );
    }
}

Mocking Exceptions

Similar to the return value mocking, you can mock a method to throw an exception.

You can mock getContactByEmail to throw an exception for specific email values...

@IsTest
public class MyTest {
    @IsTest
    static void myTestExample01() {
        Mocker mocker = Mocker.startStubbing();
        MyDependency myMock = (MyDependency) mocker.mock(MyDependency.class);

        // Mocking getContactByEmail return value
        mocker.when(myMock.getContactByEmail('[email protected]'))
            .thenReturn(new Contact(FirstName = 'Leonardo'));

        // And mocking getContactByEmail exception
        mocker.when(myMock.getContactByEmail('[email protected]'))
            .thenThrow(new MyException('Mocked exception'));

        // Going to the execution phase
        mocker.stopStubbing();

        // Getting the mocked return values
        // It will return Leonardo contact
        Contact contact1 = myMock.getContactByEmail('[email protected]');
        // It will return null
        Contact contact2 = myMock.getContactByEmail('[email protected]');

        try {
            // It will throw MyException
            myMock.getContactByEmail('[email protected]');
        } catch(Exception e) {}
    }
}

Or you can mock getContactByEmail to throw an exception for any email values...

@IsTest
public class MyTest {
    @IsTest
    static void myTestExample01() {
        Mocker mocker = Mocker.startStubbing();
        MyDependency myMock = (MyDependency) mocker.mock(MyDependency.class);

        // Mocking getContactByEmail return values
        // It's not necessary to set an email
        mocker.when(myMock.getContactByEmail(''))
             // Because the parameter value will be ignored
            .withAnyValues()
            .thenThrow(new MyException('Mocked exception'));

        // Going to the execution phase
        mocker.stopStubbing();

        // Getting the mocked return values
        try {
            // It will throw MyException
            myMock.getContactByEmail(null);
        } catch(Exception e) {}

        try {
            // It will also throw MyException
            myMock.getContactByEmail('[email protected]');
        } catch(Exception e) {}
    }
}

Mocking Void Methods

To mock void methods you will need to call the method you are mocking before the mocker.when() method calling.

@IsTest
public class MyTest {
    @IsTest
    static void myTestExample01() {
        Mocker mocker = Mocker.startStubbing();
        MyDependency myMock = (MyDependency) mocker.mock(MyDependency.class);

        // Mocking getContactByEmail return values
        // Calling the void method before to call the when() method
        myMock.insertContact(new Contact(FirstName = 'Leo'));
        // Mocking the exception
        mocker.when().thenThrow(new MyException('Mocked exception'));

        // Going to the execution phase
        mocker.stopStubbing();

        try {
            // Calling the mocked method
            // It will throw MyException
            myMock.insertContact(new Contact(FirstName = 'Leo'));
        } catch(Exception e) {}

        // Calling a not recorded method (the given Contact is not Leo!)
        // It will not throw an exception
        myMock.insertContact(new Contact(FirstName = 'Berardino'));
    }
}

Creating Expected Methods Behaviours

In some tests we want to know if our mock class has received a method call with some parameter values or how many times a method was called. The Mocker Framework allows you to specify the expected behaviours of mocked methods. Those behaviours will be asserted when you call Mocker.assert() method.

There are four method behaviours that you can assert:

  • The mocked method should be called once => .shouldBeCalledOnce();
  • The mocked method should be called N times => .shouldBeCalled(Integer times);
  • The mocked method should be never called => shouldNeverBeCalled();
  • Mock method should be called between min and max times => shouldBeCalledBetween(Integer minTimes, Integer maxTimes);

To assert if the expected behaviour in your mock class has been executed, you will need to call the mocker.assert() method. This method compares all activities received by your mock during the execution phase with the expected ones. The assertions are made using the Salesforce System.assert operations.

If you want to be notified when an assertion fails, you can call the mocker.assert(true) method with the "true" argument. The Mocker Framework will throw an exception if an assertion fails.

@IsTest
public class MyTest {
    @IsTest
    static void myTestExample01() {
        Mocker mocker = Mocker.startStubbing();
        MyDependency myMock = (MyDependency) mocker.mock(MyDependency.class);

        mocker.when(myMock.getContactByEmail('[email protected]'))
            // Mocking the return value
            .thenReturn(new Contact(FirstName = 'Leonardo'))
            // Configuring the expected behavior
            .shouldBeCalledOnce();

        mocker.when(myMock.getContactByEmail('[email protected]'))
            // Mocking the return value
            .thenReturn(new Contact(FirstName = 'Nicolas'))
            // Configuring the expected behavior
            .shouldBeCalled(3);

        // Going to the execution phase
        mocker.stopStubbing();

        // Executing the test
        Contact contact = myMock.getContactByEmail('[email protected]');
        // ...

        // Asserts
        System.asserNotEquals(null, contact);
        System.asserEquals('Leonardo', contact.FirstName);
        mocker.assert(); // Assert the expected mock behavior
    }
}

Cleaning recorded actions and behaviours

As explained, the Mocker Framework records what is expected to be done by the mocked class (stubbing phase) and all actions executed on it (execution phase). This internal state can be cleared by calling the method mocker.clear(). All previous recorded actions and behaviour will be deleted, and your mock can be stubbed again.

Mocker Utils

The Mocker Framework also provides the MockerUtils, a utility class with useful methods to help you develop your unit test class.

Generating SObjects Ids

You can generate Ids for your mock objects with the method generateId.

// First call generates the 001000000000001AAA Account Id
Id accountId1 = MockerUtils.generateId(Account.SObjectType);

// Second call generates the 001000000000002AAA Account Id
Id accountId2 = MockerUtils.generateId(Account.SObjectType);

// Generates the 003000000000001AAA Contact Id
Id contactId1 = MockerUtils.generateId(Contact.SObjectType);

// Generates the 003000000969132AAA Contact Id
Id contactId2 = MockerUtils.generateId(Contact.SObjectType, 969132);

Setting Values On Read-Only Fields

Some SObjects fields, such as formula fields, roll-up summary fields or related objects list fields are read-only and cannot be set just using the equal operator. The MockerUtils provides the method updateObjectState that allows you to easily update your SObject state even if the fields are read-only.

Account accountInstance = new Account(
    Id = MockerUtils.generateId(Account.SObjectType),
    Name = 'Test Account',
    NumberOfEmployees = 10
);

Contact contact1 = (Contact) MockerUtils.updateObjectState(
    new Contact(FirstName = 'Name'),                // The SObject instance to be updated
    new Map<String, Object>{                        // The Field Name => Data map
        'Id' => MockerUtils.generateId(Contact.SObjectType),
        'Name' => 'Leonardo Berardino',             // Read-only field
        'FirstName' => 'Leonardo',                  // Overrides the FirstName value
        'Birthdate' => Date.today().addYears(-10)   // Date field
    }
);

Contact contact2 = new Contact(
    Id = MockerUtils.generateId(Contact.SObjectType),
    FirstName = 'Another',
    LastName = 'Contact',
    Email = '[email protected]'
);

Account updatedAccount = (Account) MockerUtils.updateObjectState(
    accountInstance,                                // The SObject instance to be updated
    new Map<String, Object> {                       // The Field Name => Data map
        'Contacts' => new List<Contact> {           // Sets the read-only contacts list field
            contact1, contact2
        },
        'Website' => 'https://test.com',            // Sets the Website field value
        'Name' => 'New Account Name',               // Overrides the Name field value
        'NumberOfEmployees' => 99                   // Overrides the NumberOfEmployees field
    }
);

Unit Tests Samples

You can find several examples of unit tests using the mocking framework in the samples directory.

Issues And Feature Requests

Use the Github Issues feature to reach out if you have feedback/questions/bugs/feature requests etc.

Please do file any issue you find, keeping the following in mind:

  • Create only one issue per bug or feature request.
  • Add as much information as possible and the steps to reproduce the error.
  • Only add relevant comments to the issues. Many people get notified when a commentary is added to the issue, so let's keep them relevant.

License

BSD 3-Clause License

gs-apex-mocking-framework's People

Contributors

leonicolas avatar

Stargazers

 avatar Johnny Michel avatar .pirikiT⚆_⚆ avatar Frank Peleato avatar  avatar Fellipy Saldanha avatar Sven Francornel avatar Justus avatar Wesley Prado avatar Mehdi Kazemi avatar Shinnosuke Iwaide avatar Patrick Brinksma avatar  avatar  avatar David Giraldo avatar Gabriel Moreira avatar Luis avatar Roman avatar Johannes Fischer avatar Jéferson Chaves avatar Robert Stankiewicz avatar Miles Robson avatar  avatar Mohd Anas avatar Heber avatar

Watchers

Eduardo R Carvalho avatar  avatar  avatar Heber avatar  avatar

gs-apex-mocking-framework's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.