Code Monkey home page Code Monkey logo

fflib-apex-mocks's Introduction

FFLib ApexMocks Framework

Push Source and Run Apex Tests

ApexMocks is a mocking framework for the Salesforce Lightning Apex language.

It derives its inspiration from the well known Java mocking framework Mockito

Deploy to Salesforce

Using ApexMocks on the Salesforce Lightning Platform

ApexMocks allows you to write tests to both verify behavior and stub dependencies.

An assumption is made that you are using some form of Dependency Injection - for example passing dependencies via a constructor:

public MyClass(ClassA.IClassA dependencyA, ClassB.IClassB dependencyB)

This allows you to pass mock implementations of dependencies A and B when you want to unit test MyClass.

Lets assume we've written our own list interface fflib_MyList.IList that we want to either verify or stub:

public class fflib_MyList implements IList
{
	public interface IList
	{
		void add(String value);
		String get(Integer index);
		void clear();
		Boolean isEmpty();
	}
}

verify() behaviour verification

// Given
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_MyList.IList mockList = (fflib_MyList.IList)mocks.mock(fflib_MyList.class);

// When
mockList.add('bob');

// Then
((fflib_MyList.IList) mocks.verify(mockList)).add('bob');
((fflib_MyList.IList) mocks.verify(mockList, fflib_ApexMocks.NEVER)).clear();

If the method wasn't called the expected number of times, or with the expected arguments, verify will throw an exception. The exception message contains details of the expected and actual invocations:

EXPECTED COUNT: 1
ACTUAL COUNT: 0
METHOD: EmailService__sfdc_ApexStub.sendTo(String)
---
ACTUAL ARGS: ("[email protected]")
---
EXPECTED ARGS: [[contains "user-one"]]

when() dependency stubbing

fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_MyList.IList mockList = (fflib_MyList.IList)mocks.mock(fflib_MyList.class);

mocks.startStubbing();
mocks.when(mockList.get(0)).thenReturn('bob');
mocks.when(mockList.get(1)).thenReturn('fred');
mocks.stopStubbing();

Utilties

Setting a read-only field, such as a formula

Account acc = new Account();
Integer mockFormulaResult = 10;
acc = (Account)fflib_ApexMocksUtils.setReadOnlyFields(
		acc,
		Account.class,
		new Map<SObjectField, Object> {Account.Your_Formula_Field__c => mockFormulaResult}
);
System.assertEquals(mockFormulaResult, acc.Your_Formula_Field__c);

Stub API

Using Salesforce's Stub API, stub objects are generated dynamically at run time.

fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);

Documentation

fflib-apex-mocks's People

Contributors

afawcett avatar afawcettffdc avatar blackbaud-craigpemberton avatar chazwatkins avatar chernyy-svyat avatar claychipps avatar cropredyhelix avatar daveerickson avatar daveespo avatar dbtavernerffdc avatar dfrudd avatar dfruddffdc avatar emcdaniel-sungage avatar evanmcd avatar imjohnmdaniel avatar jthemphill avatar kjonescertinia avatar kooltra-yizhang avatar mwoodffdc avatar peterlin888 avatar phardakerffdc avatar przemekrcloudity avatar sbresin avatar stohn777 avatar wimvelzeboer avatar xapfrom avatar xonoxforce avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fflib-apex-mocks's Issues

How to .verify that a Domain layer constructor initialized with expected SobjectList

Use case:

Class A needs to construct a Domain for Opportunity and then invoke a method on that domain object

Opportunities domain = Opportunities.newInstance(someOppoList);
domain.doWork();

If you are unit testing class A and want to use ApexMocks to see if your code under test is passing the right list of opportunities to the domain object, you try in your testmethod :

...
Opportunity] mockOppos = new List<Opportunity> { ...}
((Opportunities)mocks.verify(mockOppoDomain,mocks.times(1).description('domain sb constructed w/ all Oppo in scope')))
				.newInstance(mockOppos);

But this won't compile because Opportunities.newInstance() is a static method and there is no StubAPI support for statics.

Since the pattern says to use Opportunities domain = Opportunities.newInstance(someOppoList); that in turn resolves to:

public static IOpportunities newInstance(List<Opportunity> sObjectList){
    	return (IOpportunities) Application.Domain.newInstance(sObjectList);
  }

...I'm a bit puzzled how to use ApexMocks to see if my mockDomain object was constructed with the expected list of sobjects.

N.B. relevant Mockito Stackoverflow seems to suggest this isn't going to be possible.

Is there an alternative besides moving the domain method to a Service layer?

Removing some clutter from ApexMocks syntax

Hey guys,

I recently started working with apex-mocks and I love it. The only thing I didn't like and where it falls a bit short with regards to Mockito is the amount of clutter in the code. Some is due to the Salesforce restrictions (no generics...) but some seemed unnecessary.

So I played arround with it a bit and found three changes that improve this. The results can be seen here: https://github.com/mischkes/fflib-apex-mocks/tree/feature/poc-for-less-clutter#a-more-complicated-case

We are already using this code in production, but it is not yet library-grade. If you are interested I would be happy to improve the code and make some pull requests.

The changes are:

Create a Utility class that encapsulates ApexMocks and Match

  • See https://github.com/mischkes/fflib-apex-mocks/blob/feature/poc-for-less-clutter/src/classes/fflib_Mock.cls
  • this is actually the same way Mockito is doing it
  • I would advise Projects using fflib-apex-mocks to rename this file (eg. to 'Mocks' as in the example or even 'Mk') to further reduce clutter. This is easily possible as there are no dependencies on fflib_Mock, so no other class has to be changed.
  • This removes the clutter from constructing fflib_ApexMocks
  • This removes clutter from writing fflib_Match
  • This removes more clutter for more complex use-cases, e.g. by the use of fflib_Mock.Answer)
  • This also creates a single-place-of use, which makes it easier for Newbies. "Just look at fflib_Mock"
  • This also hides away some of the public Methods intended for use internally, like handleMethodCall or mockNonVoidMethod
  • This change requires almost no change to the existing code. Currently I'm subclassing fflib_Match from fflib_Mock for convenienve, but event that could be wrapped. Which might be a good Idea to hide some internal methods like getAndClearMatchers.

Make start/stopStubbing unnecessary

  • there is/was probably some use-case why those methods makes sense, but I didn't see it
  • the main change was to always return a value in fflib_ApexMocks.mockNonVoidMethod, and move the actual parameter storing to fflib_ApexMocks.when
  • This remove the clutter from the startStubbing() / stopStubbing() methods
  • this is a minor breaking change, as mocking a method that was previously mocked to throw an exception will throw this exception during mocking. Mockito uses doReturn() statements for this case, which could be implemented
  • this also removes the curent possibility to check for incomplete stubbing. I guess it could still be done by setting a flag inside when() and clearing it on then()

Record call history inside methods

  • this change is probably mostly a matter of taste
  • I never really liked the way complex argument checks are done in Mockito, with the ArgumentCaptor and all. And ApexMocks is following Mockito here.
  • I really like the way jasmine is handling this and implemented it in the same way: simply storing the call history in the method (here: the Method mock object)
  • this removes the clutter to create the captor, then verifying, and then getting the object from the captor
  • this was also a minor change in the code

All in all these changes remove approx. 50% of the necessary mocking code, which I think is really a lot.

IDGenerator support for xxxHistory SObjects

Currently, fflib_IDGenerator.generate(ContactHistory.SObjectType) returns null000000000000 for any xxxHistory object because getDescribe().getKeyPrefix() returns null for these SObjects

Extend the class's generate method to look like this:

 public static Id generate(Schema.SObjectType sobjectType) {
   String keyPrefix = sobjectType.getDescribe().getKeyPrefix();
   if (keyPrefix == null) {
    keyPrefix =  sobjectType.getDescribe().getName().endsWith('History') ? '017' : null;
   }

   fakeIdCount++;
   String fakeIdPrefix = ID_PATTERN.substring(0, 12 - fakeIdCount.format().length());
   return Id.valueOf(keyPrefix + fakeIdPrefix + fakeIdCount);
 }

While mocking xxxHistory records is problematic as you can't mock OldValue or NewValue fields including via Json.deserialize, you can mock the other fields (Id, ParentId, Field) via Json.deserialize and hence can produce selector mocks for use cases such as testing Field History deletion (introduced in V42)

Matcher sObjectWith doesn't tell you why the match failed

While matcher sObjectWith is pretty useful, if you are verifying several fields, it doesn't tell you which one failed and why. This is a step backwards from traditional asserts and makes debugging more difficult

Example 1

((fflib_SobjectUnitOfWork) mocks.verify(mockUow,
           mocks.times(1).description('some modestly helpful text')))
    	.registerDirty(fflib_Match.sObjectWith(new map<SObjectField,Object> {
    					OrderItem.ID => mockOrders.OrderItems[0].Id,
    					OrderItem.Foo__c => expectedFoo,
    					OrderItem.Bar__c => expectedBar}));

This generates

fflib_ApexMocks.ApexMocksException: Expected : 1, Actual: 0 -- 
Wanted but not invoked: fflib_SObjectUnitOfWork__sfdc_ApexStub.registerDirty(SObject). 
some modestly helpful text.

So, what is a developer to do?

  • Is the ID a mismatch?
  • Is the Foo__c a mismatch?
  • Is the Bar__c a mismatch?

The developer has to go the system.debug route or futz around with argument capture which is heavy syntax.

The most useful behavior would be for sObjectWith to look through all of the fields and concatenate all the mismatches into a string for display in the exception message. Thus, the developer could track down and fix all of the offending items, not discover them 1x1.

Root cause is the method return for a fflib_MatcherDefinitions.someMatcher. These methods simply return true or false and hence lose (valuable) information.

I can think of some hack solutions - like sObjectWith deferring the return of false until after it has emitted a System.debug(LoggingLevel.FATAL,the concatenation of mismatches) so at least there is a fast way to find the issues without recompiling anything; but better would be to surface in the exception message.

Multiple selector mocks

How do you use multiple selector mocks with ffib ApexMock? In the example below, they use only one selector mock: IOpportunitiesSelector selectorMock = new Mocks.OpportunitiesSelector(mocks);
But how should it look like if the applyDiscounts method used two selectors to query its data, like Opportunites and Accounts (selectById)?

Any thoughts?

@IsTest
private static void callingServiceShouldCallSelectorApplyDiscountInDomainAndCommit()
{
    // Create mocks
    fflib_ApexMocks mocks = new fflib_ApexMocks();
    fflib_ISObjectUnitOfWork uowMock = new fflib_SObjectMocks.SObjectUnitOfWork(mocks);
    IOpportunities domainMock = new Mocks.Opportunities(mocks);
    IOpportunitiesSelector selectorMock = new Mocks.OpportunitiesSelector(mocks);

    // Given
    mocks.startStubbing();
    List<Opportunity> testOppsList = new List<Opportunity> { 
        new Opportunity(
            Id = fflib_IDGenerator.generate(Opportunity.SObjectType),
            Name = 'Test Opportunity',
            StageName = 'Open',
            Amount = 1000,
            CloseDate = System.today()) };
    Set<Id> testOppsSet = new Map<Id, Opportunity>(testOppsList).keySet();
    mocks.when(domainMock.sObjectType()).thenReturn(Opportunity.SObjectType);
    mocks.when(selectorMock.sObjectType()).thenReturn(Opportunity.SObjectType);
    mocks.when(selectorMock.selectByIdWithProducts(testOppsSet)).thenReturn(testOppsList);
    mocks.stopStubbing();
    Decimal discountPercent = 10;
    Application.UnitOfWork.setMock(uowMock);
    Application.Domain.setMock(domainMock);
    Application.Selector.setMock(selectorMock);

    // When
    OpportunitiesService.applyDiscounts(testOppsSet, discountPercent);

    // Then
    ((IOpportunitiesSelector) 
        mocks.verify(selectorMock)).selectByIdWithProducts(testOppsSet);
    ((IOpportunities) 
        mocks.verify(domainMock)).applyDiscount(discountPercent, uowMock);
    ((fflib_ISObjectUnitOfWork) 
        mocks.verify(uowMock, 1)).commitWork();
}

Small enhancement to ReadMe

For those of us who didn't come from a Java background and hence weren't exposed to Mockito, the Readme section when() dependency stubbing is maddeningly tantalizing. The obvious question one immediately asks is what if you don't care what the arg values are in the stubbed method? After much web searching and inspection of fflib_ApexMocksTest I realized there was a whole wealth of matchers that can be used not only in the verify() but also in the when()!

A small additional example in the Readme would inspire and direct the reader to the supported matchers in fflib_Match.

This framework is pretty powerful stuff but as

  • the related SFDC andyinthecloud pages are out of date (ref to generating Mocks class) and
  • the second edition book has syntax errors (uses factory() method instead of mock() method) and
  • the referenced blog posts are sprinkled with pre Stub API ....

Thus, getting the fflib / andyinthecloud online doc up to date / more comprehensive would go a long way towards increased adoption.

Spurious/scary SobjectExceptions in debug log for Matcher SObjectWith

If your code does

uow.registerDirty(someAccount);
uow.registerDirty(someCase);

and you have these verifies:


((fflib_SobjectUnitOfWork) mocks.verify(mockUow,mocks.times(1)))
    		.registerDirty(fflib_Match.sObjectWith(new map<SObjectField,Object> {																
                      Account.ID => someAccountId}));

((fflib_SobjectUnitOfWork) mocks.verify(mockUow,mocks.times(1)))
    		.registerDirty(fflib_Match.sObjectWith(new map<SObjectField,Object> {																
                      Case.ID => someCaseId}));

Then the debug log will show these exceptions

System.SObjectException: Account.Id does not belong to SObject type Case
System.SObjectException: Case.Id does not belong to SObject type Account				

Investigation:

  • The Matcher for SObjectWith catches exception System.SObjectException and returns false as comparing a request to match Account for the registerDirty call on Case is in fact a no match (and vice-versa)

Hence ...

  • While the code is WAD and even commented as a legit exception, the debug log to the casual debugger looks like something bad has happened and needs investigation

Options

  • No good ones.
  • The toMatch is of type Map<Schema.SObjectField, Object> which doesn't tell you the type of Sobject the map is intended to match since there is no method to go from a Schema.SObjectField to an SObjectType (and this presumes that toMatch is constructed (sensibly) with only fields for the same SObjectType ..admittedly a nit)
  • Ideally, one would compare toMatch's implicit SObjectType with soArg.getSobjectType() and if different, return false and avoid throwing the SObjectExceptions.

Intermittent unit test failures in Test_ApexMocksTest in Spring '16 orgs

I recently deployed the latest fflib-apex-mocks code into a couple of different Spring '16 Developer Edition org. When I tried running tests in the fflib_ApexMocksTest class, I got a number of failures. I've included details of the failures I received in the table at the bottom of this issue description. I found that these failures appeared to be intermittent, and in Spring '16 orgs only. After some debugging of one particular failure (PatronTicket.fflib_ApexMocksTest.whenStubExceptionTheExceptionShouldBeThrown: line 222, column 1), we discovered what appears to be a platform bug with that causes intermittent failures with statements that use the instanceof operator, like System.assert(ex instanceof MyException) statements. This particular assertion expects the caught Exception to be an "instanceof" the MyException class. Sometimes it is, and sometimes it isn't ;) I have opened a case with Salesforce on this, but I wanted to log it here as an issue, so that others using the fflib-apex-mocks framework are aware of this.

Method name Message Stack trace
whenStubSingleCallWithSingleArgumentShouldReturnStubbedValue System.TypeException: Invalid conversion from runtime type String to java.lang.Exception Class.PatronTicket.fflib_ApexMocks.mockNonVoidMethod: line 248, column 1 Class.PatronTicket.fflib_Mocks.Mockfflib_MyList.get: line 21, column 1 Class.PatronTicket.fflib_ApexMocksTest.whenStubSingleCallWithSingleArgumentShouldReturnStubbedValue: line 41, column 1
whenStubSameCallWithDifferentArgumentValueShouldReturnLastStubbedValue System.TypeException: Invalid conversion from runtime type String to java.lang.Exception Class.PatronTicket.fflib_ApexMocks.mockNonVoidMethod: line 248, column 1 Class.PatronTicket.fflib_Mocks.Mockfflib_MyList.get: line 21, column 1 Class.PatronTicket.fflib_ApexMocksTest.whenStubSameCallWithDifferentArgumentValueShouldReturnLastStubbedValue: line 83, column 1
whenStubVoidMethodWithExceptionAndCallMethodTwiceThenExceptionShouldBeThrownTwice System.AssertException: Assertion Failed: Stubbed exception should have been thrown. Class.PatronTicket.fflib_ApexMocksTest.whenStubVoidMethodWithExceptionAndCallMethodTwiceThenExceptionShouldBeThrownTwice: line 306, column 1
whenStubVoidMethodWithExceptionThenExceptionShouldBeThrown System.AssertException: Assertion Failed: Stubbed exception should have been thrown. Class.PatronTicket.fflib_ApexMocksTest.whenStubVoidMethodWithExceptionThenExceptionShouldBeThrown: line 242, column 1
whenStubMultipleCallsWithMultipleArgumentShouldReturnStubbedValues System.TypeException: Invalid conversion from runtime type String to java.lang.Exception Class.PatronTicket.fflib_ApexMocks.mockNonVoidMethod: line 248, column 1 Class.PatronTicket.fflib_Mocks.Mockfflib_MyList.get2: line 26, column 1 Class.PatronTicket.fflib_ApexMocksTest.whenStubMultipleCallsWithMultipleArgumentShouldReturnStubbedValues: line 370, column 1
whenStubMultipleCallsWithSingleArgumentShouldReturnStubbedValues System.TypeException: Invalid conversion from runtime type String to java.lang.Exception Class.PatronTicket.fflib_ApexMocks.mockNonVoidMethod: line 248, column 1 Class.PatronTicket.fflib_Mocks.Mockfflib_MyList.get: line 21, column 1 Class.PatronTicket.fflib_ApexMocksTest.whenStubMultipleCallsWithSingleArgumentShouldReturnStubbedValues: line 60, column 1
whenStubCallWithNoArgumentsShouldReturnStubbedValue System.TypeException: Invalid conversion from runtime type Boolean to java.lang.Exception Class.PatronTicket.fflib_ApexMocks.mockNonVoidMethod: line 248, column 1 Class.PatronTicket.fflib_Mocks.Mockfflib_MyList.isEmpty: line 36, column 1 Class.PatronTicket.fflib_ApexMocksTest.whenStubCallWithNoArgumentsShouldReturnStubbedValue: line 101, column 1
whenStubExceptionTheExceptionShouldBeThrown System.AssertException: Assertion Failed Class.PatronTicket.fflib_ApexMocksTest.whenStubExceptionTheExceptionShouldBeThrown: line 222, column 1
whenStubMultipleVoidMethodsWithExceptionsThenExceptionsShouldBeThrown System.AssertException: Assertion Failed: Stubbed exception should have been thrown. Class.PatronTicket.fflib_ApexMocksTest.whenStubMultipleVoidMethodsWithExceptionsThenExceptionsShouldBeThrown: line 268, column 1
stubAndVerifyMethodCallsWithNoArguments System.TypeException: Invalid conversion from runtime type Boolean to java.lang.Exception Class.PatronTicket.fflib_ApexMocks.mockNonVoidMethod: line 248, column 1 Class.PatronTicket.fflib_Mocks.Mockfflib_MyList.isEmpty: line 36, column 1 Class.PatronTicket.fflib_ApexMocksTest.stubAndVerifyMethodCallsWithNoArguments: line 195, column 1

Test_ApexMocksTest failure in namespaced packager orgs

We recently had to deploy the fflib-apex-mocks classes into one of our namespaced package development orgs because we updated to the latest fflib-apex-common classes, and that introduced a dependency on fflib-apex-mocks. When I run the unit tests for the fflib-apex-mocks classes, I get the following error from Test_ApexMocksTest:

Error message: System.AssertException: Assertion Failed: Expected: Invalid conversion from runtime type fflib_ApplicationTest.ContactsConstructor to fflib_SObjectDomain.IConstructable, Actual: Invalid conversion from runtime type PatronTicket.fflib_ApplicationTest.ContactsConstructor to PatronTicket.fflib_SObjectDomain.IConstructable

Stack trace: Class.PatronTicket.fflib_ApplicationTest.callingDomainFactoryWithContructorClassThatDoesNotSupportIConstructableShouldGiveException: line 191, column 1

There are two assertions in this test method that are looking for exact string matches on the exception message, but when run in a namespaced org, the exception message doesn't match because the fflib classes are prefixed with the package namespace. I made the following change to use a regular expression that is tolerant of the package namespace prefix in order to check the exception messages:

    @IsTest
    private static void callingDomainFactoryWithContructorClassThatDoesNotSupportIConstructableShouldGiveException()
    {
        try {
            Domain.newInstance(new List<Contact>{ new Contact(LastName = 'TestContactLName') });
            System.assert(false, 'Expected exception');
        } catch (System.TypeException e) {
            System.assert(Pattern.Matches('Invalid conversion from runtime type \\w*\\.?fflib_ApplicationTest\\.ContactsConstructor to \\w*\\.?fflib_SObjectDomain\\.IConstructable',
                e.getMessage()), 'Exception message did not match the expected pattern: ' + e.getMessage());
//          System.assertEquals('Invalid conversion from runtime type fflib_ApplicationTest.ContactsConstructor to fflib_SObjectDomain.IConstructable', e.getMessage());
        }   

        try {
            Domain.newInstance(new List<SObject>{ new Contact(LastName = 'TestContactLName') }, Contact.SObjectType);
            System.assert(false, 'Expected exception');
        } catch (System.TypeException e) {
            System.assert(Pattern.Matches('Invalid conversion from runtime type \\w*\\.?fflib_ApplicationTest\\.ContactsConstructor to \\w*\\.?fflib_SObjectDomain\\.IConstructable2',
                e.getMessage()), 'Exception message did not match the expected pattern: ' + e.getMessage());
//          System.assertEquals('Invalid conversion from runtime type fflib_ApplicationTest.ContactsConstructor to fflib_SObjectDomain.IConstructable2', e.getMessage());
        }       
    }   

I can submit a pull request for this if you like.

fflib_MethodArgValues equals method not working correctly.

I suspect this mainly has to do with SFDC's implementation of equals for the List class as that is what the class relies on for it's equals (argValues == argValues). I believe is boils down to the list equals method not working correctly when comparing objects instantiated with SObjectType.newSObject and their regular constructors.

I have a class that instantiates a List<SObject> and populates its contents via SObjectType.newSObject(Id) which then gets passed to a Repository class which I use to update the records, and this class is what I mock and verify against that the right values were passed to it to update. If I do not verify a list with contents instantiated via newSObject equals will fail even though the values are equivalent.

I've provided my test below to show how I'm verifying. Using the commented functionality before the verify call will cause an ApexMocksException to be thrown because ff_lib_MethodArgValues.equals(Object) returns false even though equivalent values will be passed to it. I've verified this via debug logs:

12:25:18.1 (143519714)|USER_DEBUG|[84]|DEBUG|in count calls
12:25:18.1 (143608102)|USER_DEBUG|[85]|DEBUG|fflib_MethodArgValues:[argValues=((Account:{Id=001000000000001AAA, Number_of_Child_Accounts__c=200}))]
12:25:18.1 (143660625)|USER_DEBUG|[86]|DEBUG|((Account:{Id=001000000000001AAA, Number_of_Child_Accounts__c=200}))
12:25:18.1 (143827303)|USER_DEBUG|[90]|DEBUG|in arg values
12:25:18.1 (143880279)|USER_DEBUG|[91]|DEBUG|fflib_MethodArgValues:[argValues=((Account:{Id=001000000000001AAA, Number_of_Child_Accounts__c=200}))]
12:25:18.1 (143926744)|USER_DEBUG|[92]|DEBUG|((Account:{Id=001000000000001AAA, Number_of_Child_Accounts__c=200}))
12:25:18.1 (144008510)|USER_DEBUG|[48]|DEBUG|in other
12:25:18.1 (144040162)|USER_DEBUG|[56]|DEBUG|comparing arg values
12:25:18.1 (144074134)|USER_DEBUG|[57]|DEBUG|((Account:{Id=001000000000001AAA, Number_of_Child_Accounts__c=200}))
12:25:18.1 (144102713)|USER_DEBUG|[58]|DEBUG|((Account:{Id=001000000000001AAA, Number_of_Child_Accounts__c=200}))
12:25:18.1 (144118216)|USER_DEBUG|[59]|DEBUG|using equals
12:25:18.1 (144314303)|USER_DEBUG|[60]|DEBUG|false
12:25:18.1 (146248321)|EXCEPTION_THROWN|[81]|fflib_ApexMocks.ApexMocksException: Expected : 1, Actual: 0 -- Wanted but not invoked: Repository__sfdc_ApexStub.upd(List<SObject>).
@IsTest
    static void childAccountCountShouldBeRolledIntoParentAccount() {
        fflib_ApexMocks mocks = new fflib_ApexMocks();
        Repository repo = (Repository) mocks.mock(Repository.class);
        RollupSelector sel = (RollupSelector) mocks.mock(RollupSelector.class);
        Id parentId = fflib_IDGenerator.generate(Account.SObjectType);
        
        Map<Id, Account> trgNewMap = new Map<Id, Account>();
        for (Integer i = 0; i < 200; i++) {
            Id accountId = fflib_IDGenerator.generate(Account.SObjectType);
            trgNewMap.put(accountId, new Account(Id = accountId, ParentId = parentId));
        }

        mocks.startStubbing();
        mocks.when(sel.findByLookupIdIn(new Set<Id>{ parentId }))
             .thenReturn(trgNewMap.values());
        mocks.stopStubbing();

        Test.startTest();
        AccountAfterExec exec = new AccountAfterExec(repo, sel, trgNewMap, null);
        exec.execute();
        Test.stopTest();

        /*
         * We have to instantiate the verify list the same way
         * we do in the Rollup class as there seems to be a bug
         * in the equals method when instantiating with `newSobject`
         * and doing it regularly.
         */
        List<SObject> ret = new List<SObject>();
        SObject acc = Account.SObjectType.newSObject(parentId);
        acc.put(Account.Number_of_Child_Accounts__c.getDescribe().getName(), 200);

        ret.add(acc);

        /* THIS DOES NOT WORK
            List<SObject> ret = new List<SObject>{ new Account(
            Id = parentId,
            Number_of_Child_Accounts__c = 200
        )}; */
        ((Repository) mocks.verify(repo)).upd(ret);
    }

Please let me know if I have no been clear enough or if you require more details.

ID Generator fails on 1000th call due to use of Integer.format().

Hey Apex Mocks team. First of all, thanks for the fantastically useful library and all your hard work.

I'm utilizing the ID generator in Apex Mocks. We're bulk testing where we are generating two hundred of each of several SObjects. This leads to the generator producing over 999 Ids. It then fails due to the , added by the format method.

I have fixed this in our fork by utilizing String.valueOf() rather than Integer.format().
fflib_IDGenerator.cls Line 39
String fakeIdPrefix = ID_PATTERN.substring(0, 12 - fakeIdCount.format().length());
becomes
String fakeIdPrefix = ID_PATTERN.substring(0, 12 - String.valueOf(fakeIdCount).length());

I'm wondering if there is some reason that Integer.format() was used, which I may have missed. I just want to make sure I'm not causing another bug while I fix this one.

Thanks

p.s. Easy anonymous test code:

Set<Id> testIds = new Set<Id>();
for(Integer i = 0; i < 1000; i++){
    Id testId = fflib_IDGenerator.generate(Contact.SObjectType);
    System.debug('Test ' + i + ' Id: ' + testId);
	testIds.add(testId);
}
System.debug('Test Id Set: ' + testIds);

Mock static methods not supported

I dont think static method mocking is supported currently.
Java Mockito has a method to mock static methods like PowerMock.mockStatic(), but couldn't find a way to mock my static methods in a non static class?

Matcher sObjectWith doesn't work with uow.registerXXX(list)

The useful Matcher sObjectWith can't be used when attempting to verify the inputs to these:

uow.registerNew(someListOfSobjects); or
uow.registerDirty(someListOfSobjects);

This is because the sObjectWith matcher is a singleton SObject and the argument passed to the uow methods is a list.

What would be nice is a Matcher that would compare an expected SObject w/ field vals to see if any of the Sobjects passed to a uow.registerXXX(someList) had an sobject with those fieldValues.

This could, I suppose, be extended to a Matcher that was a list of expected Sobjects w/ field values and would ensure that the list argument passed to uow.registerXXX had all of the expected Sobjects.

Basically, if the code under test is using the uow.registerXXX(someList) methods, there is no current way without resorting to ArgumentCapture to verify that the code under test called uow with the expected sobject(s). Or, put another way, since ApexMocks exists to support fflib (among other use cases), then supporting the uow.registerXXX(someList) should be supported with a Matcher.

Failure in Apex Mock Deploy

Deployment Started
Status: Queued
Status: Completed
Deployment Complete
Failures:
deploy/classes/fflib_ApexMocksUtils.cls(162,14):Method does not exist or incorrect signature: void writeFieldName(String) from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(163,14):Method does not exist or incorrect signature: void writeStartObject() from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(164,14):Method does not exist or incorrect signature: void writeNumberField(String, Integer) from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(165,14):Method does not exist or incorrect signature: void writeBooleanField(String, Boolean) from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(166,14):Method does not exist or incorrect signature: void writeFieldName(String) from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(168,14):Method does not exist or incorrect signature: void writeEndObject() from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(133,17):Illegal assignment from System.JSONGenerator to Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(137,42):Method does not exist or incorrect signature: void getAsString() from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(188,14):Method does not exist or incorrect signature: void writeStartArray() from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(192,14):Method does not exist or incorrect signature: void writeStartObject() from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(196,14):Method does not exist or incorrect signature: void writeFieldName(String) from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(202,14):Method does not exist or incorrect signature: void writeString(String) from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(204,14):Method does not exist or incorrect signature: void writeEndObject() from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtils.cls(208,14):Method does not exist or incorrect signature: void writeEndArray() from the type Jsongenerator
deploy/classes/fflib_ApexMocksUtilsTest.cls(162,14):Dependent class is invalid and needs recompilation:
Class fflib_ApexMocksUtils : Method does not exist or incorrect signature: void writeFieldName(String) from the type Jsongenerator
deploy/classes/fflib_MatchTest.cls(162,14):Dependent class is invalid and needs recompilation:
Class fflib_ApexMocksUtilsTest : Dependent class is invalid and needs recompilation:
Class fflib_ApexMocksUtils : Method does not exist or incorrect signature: void writeFieldName(String) from the type Jsongenerator
deploy/classes/fflib_MatcherDefinitionsTest.cls(162,14):Dependent class is invalid and needs recompilation:
Class fflib_ApexMocksUtilsTest : Dependent class is invalid and needs recompilation:
Class fflib_ApexMocksUtils : Method does not exist or incorrect signature: void writeFieldName(String) from the type Jsongenerator
classes/fflib_SystemTest.cls
classes/fflib_Inheritor.cls-meta.xml
classes/fflib_MatchTest.cls
classes/fflib_Answer.cls
classes/fflib_MethodArgValuesTest.cls
classes/fflib_InheritorTest.cls-meta.xml
classes/fflib_AnyOrder.cls
classes/fflib_InheritorTest.cls
classes/fflib_AnyOrderTest.cls-meta.xml
classes/fflib_ApexMocksUtilsTest.cls-meta.xml
classes/fflib_ApexMocksConfig.cls-meta.xml
classes/fflib_ApexMocksUtilsTest.cls
classes/fflib_MethodVerifier.cls-meta.xml
classes/fflib_SystemTest.cls-meta.xml
classes/fflib_ArgumentCaptor.cls
classes/fflib_ApexMocks.cls-meta.xml
classes/fflib_AnswerTest.cls-meta.xml
classes/fflib_MethodVerifier.cls
classes/fflib_MethodCountRecorder.cls-meta.xml
classes/fflib_Mocks.cls
classes/fflib_MyList.cls-meta.xml
classes/fflib_ArgumentCaptorTest.cls
classes/fflib_MethodArgValuesTest.cls-meta.xml
classes/fflib_ArgumentCaptorTest.cls-meta.xml
classes/fflib_MethodReturnValue.cls
classes/fflib_ApexMocksConfig.cls
classes/fflib_MatcherDefinitions.cls-meta.xml
classes/fflib_ApexMocks.cls
classes/fflib_MethodReturnValueRecorder.cls-meta.xml
classes/fflib_MethodReturnValue.cls-meta.xml
classes/fflib_Match.cls-meta.xml
classes/fflib_MatchersReturnValue.cls
classes/fflib_AnyOrder.cls-meta.xml
classes/fflib_InvocationOnMock.cls
classes/fflib_AnyOrderTest.cls
classes/fflib_QualifiedMethodAndArgValues.cls-meta.xml
classes/fflib_ApexMocksUtils.cls-meta.xml
classes/fflib_QualifiedMethod.cls-meta.xml
classes/fflib_InOrder.cls-meta.xml
classes/fflib_MethodReturnValueRecorder.cls
classes/fflib_ArgumentCaptor.cls-meta.xml
classes/fflib_Mocks.cls-meta.xml
classes/fflib_VerificationMode.cls-meta.xml
classes/fflib_ApexMocksUtils.cls
classes/fflib_System.cls-meta.xml
classes/fflib_Match.cls
classes/fflib_Answer.cls-meta.xml
classes/fflib_IMatcher.cls-meta.xml
classes/fflib_ApexMocksTest.cls
classes/fflib_MethodArgValues.cls-meta.xml
classes/fflib_QualifiedMethodAndArgValues.cls
classes/fflib_MatcherDefinitionsTest.cls
classes/fflib_MyList.cls
classes/fflib_IDGenerator.cls
classes/fflib_Inheritor.cls
classes/fflib_InOrderTest.cls-meta.xml
classes/fflib_InOrder.cls
classes/fflib_MethodCountRecorder.cls
classes/fflib_AnswerTest.cls
classes/fflib_QualifiedMethodTest.cls-meta.xml
classes/fflib_IMatcher.cls
classes/fflib_ApexMocksTest.cls-meta.xml
classes/fflib_MethodArgValues.cls
classes/fflib_VerificationMode.cls
classes/fflib_IDGeneratorTest.cls
classes/fflib_QualifiedMethod.cls
classes/fflib_IDGenerator.cls-meta.xml
classes/fflib_IDGeneratorTest.cls-meta.xml
classes/fflib_MatcherDefinitions.cls
classes/fflib_MatchersReturnValue.cls-meta.xml
classes/fflib_InOrderTest.cls
classes/fflib_MatchTest.cls-meta.xml
classes/fflib_MatcherDefinitionsTest.cls-meta.xml
classes/fflib_QualifiedMethodTest.cls
classes/fflib_InvocationOnMock.cls-meta.xml
classes/fflib_System.cls
package.xml

fflib_MethodVerifier could use more diagnostics

the method throwException currently displays

Expected n, Actual m -- Wanted by not invoked <the qualified method> <custom message>

which can lead to head scratching when one tries to figure out why the verify fails.

  • Is the matcher wrong in the testmethod?
  • Is the code under test wrong?
  • Are the calls in the wrong order (fflib_InOrder use case)?

Since ApexMocks is recording all the actual method calls and the verify methods are comparing a single method call against all recorded calls of that method; it would seem useful to the developer to display an enumeration of the recorded method calls when this exception is thrown.

Here's an example where this would have helped me (example is a simplified version of the real code under test)

Code Under Test

public with sharing class MyCode {

	public void doStuff(List<Id> list0, List<Id> list1) {

		StuffService.add(list0);
		list0.clear();  // pay attention to this line
		StuffService.add(list1);
	}
} 

Service class being mocked

public with sharing class StuffServiceImpl implements IStuffService {
	static List<id> ids = new List<Id>();

	public void add(List<Id> idsToAdd) {
		ids.addAll(idsToAdd);
	}
}

Test method

@IsTest
private class MyCodeTest {
  @IsTest
  static void testInorder() {

    //	Given test data
    Id[] mockIds = new List<Id> {
			fflib_IDGenerator.generate(Account.SObjectType),
			fflib_IDGenerator.generate(Account.SObjectType),
			fflib_IDGenerator.generate(Account.SObjectType),
			fflib_IDGenerator.generate(Account.SObjectType)
    };
    //	Given mocks framework
    fflib_ApexMocks mocks = new fflib_ApexMocks();

    // Given mock Service
    StuffServiceImpl mockStuffService = (StuffServiceImpl) mocks.mock(StuffServiceImpl.class);
    // Given mock Service injected
    Application.Service.setMock(IStuffService.class,mockStuffService);

    //	Given inOrderMocks
    fflib_InOrder mocksInOrder = 
       new fflib_InOrder(mocks,new List<Object> {mockStuffService});

    //	Given code to test
    MyCode myCode = new MyCode();

    //	When called with distinct sets of Ids
    myCode.doStuff(new List<id> {mockIds[0],mockIds[1]},
				new List<Id> {mockIds[2],mockIds[3]});

    //	Then verify order of calls to mock service
   ((StuffServiceImpl)mocksInOrder.verify(mockStuffService,mocks.calls(1)
				.description('service sb called for ids[0] and [1]')))
				.add(new List<Id> {mockIds[0],mockIds[1]});
    ((StuffServiceImpl)mocksInOrder.verify(mockStuffService,mocks.calls(1)
				.description('service sb called for ids[2] and [3]')))
				.add(new List<Id> {mockIds[2],mockIds[3]});

  }
}

The first verify fails with

fflib_ApexMocks.ApexMocksException: In Order: Expected : 1, Actual: 0 -- Wanted but not invoked: StuffServiceImpl__sfdc_ApexStub.add(List<Id>). service sb called for ids[0] and [1].

...head scratching...

The reason why is that ApexMocks captures method non-primitive arguments by reference (rather than cloning them -- which is admittedly not always possible), so if the code under test changes the captured non-primitive argument before the ApexMocks verify is executed, the argument being compared (in my case):


((StuffServiceImpl)mocksInOrder.verify(mockStuffService,mocks.calls(1)
				.description('service sb called for ids[0] and [1]')))
				.add(new List<Id> {mockIds[0],mockIds[1]});

is no longer is equal to the argument that was captured because the code under test had cleared that argument! with list0.clear();

Five hours of my life went into discovering this after debugging the guts of ApexMocks and seeing that fflib_InOrder.verifyMethodCall was returning via getNextMethodCall() an argument of an empty list even though during method recording, the recorded arg was a list of two Ids. I should not have to dig into ApexMocks internals to see why the matching failed.

Method call counts persist for entire testMethod process

Inside a testMethod, we loop through a list of TestCases, stub dependencies, and verify behavior. The problem we've encountered is the verify call will fail on the second test case instance saying that the method was expected to be called once but actually was called twice. Instantiating ApexMocks and the mock dependencies inside the loop doesn't work. What does work is if we pass unique inputs to the mock method we are verifying for each test case instance.

Example

static testMethod void getUsersTest() {
    for (TestCase testCase : getTestCases()) {
        fflib_ApexMocks mocks = new fflib_ApexMocks();

        IUserRepository mockUserRepository = (IUserRepository)mocks.mock(IUserRepository.class);

        [...]
        ((IUserRepository) mocks.verify(mockUserRepository, 1)).getUsers(testCase.nameFilter);
        [...]
    }
}
 
private class TestCase {
    string nameFilter;
    integer someOtherFilter;
 
    TestCase(string nameFilter, integer someOtherFilter) {
        this.nameFilter = nameFilter;
        this.someOtherFilter = someOtherFilter;
    }
}

These test cases would fail:

private static List<TestCase> getTestCases() {
ย ย ย ย return new List<TestCase>{
ย ย ย ย ย ย ย ย new TestCase('Bob', null),
ย ย ย ย ย ย ย ย new TestCase('Bob', 0),
ย ย ย ย ย ย ย ย new TestCase('Bob', 5),
ย ย ย ย };
}

And these would succeed:

private static List<TestCase> getTestCases() {
ย ย ย ย return new List<TestCase>{
ย ย ย ย ย ย ย ย new TestCase('Bob', null),
ย ย ย ย ย ย ย ย new TestCase('Bib', 0),
ย ย ย ย ย ย ย ย new TestCase('Berb', 5),
ย ย ย ย };
}

Possible cause

After some digging into the library, I may have found what's causing this. It looks like the library is using fflib_MethodCountRecorder.methodArgumentsByTypeName to keep track of the method calls, but since it's static it persists for the entire testMethod process.

sObjectWith Matcher issue when uow.registerNew, uow.registerDirty on same object

I'm not sure there is an answer to this but consider the following (assumes fflib Unit Of Work pattern)

Account a = new Account (Name = 'Foo');
uow.registerNew(a);
a.Name = 'Bar';
uow.registerDirty(a);

and this verify ...

((fflib_SobjectUnitOfWork) mocks.verify(mockUow,mocks.times(1)
                                              .description('1 Account sb inserted')))
	.registerNew(fflib_Match.sObjectWith(new map<SObjectField,Object> {
					Account.Name => 'Foo'
			}));

The verify actually fails because the in-memory version of the Account a used in the uow.registerNew has changed after the registerNew was performed. When the mocks.verify executes, a.Name = 'Bar' !

This was a bit counterintuitive to me as I was expecting the mocking system to capture the arguments at the time of the uow.registerNew(a) and thus be verifiable later.

Now, why did this issue come up? I had a bulk processor that was accepting events from a 3rd party system. Within the transaction, new Accounts would be created (uow.registerNew) but if the same account was found later in the batch, a uow.registerDirty would be done on the Sobject held in memory from the insert. I relied on the fact that fflib_SObjectUnitOfWork does inserts first, then updates, so the coding pattern worked well.

Obviously, I can rework the verify statement but if there's a technical solution to this, that would be great.

methodRecorder not correct if methodArg in Map changes after recording

The method recording uses a Object Map to store method arguments that were passed to a method so that verification can be performed. Since a method argument could be a reference to an Object (e.g. SObject, Apex class, etc.), only a shallow reference is maintained. Therefore, if the referenced object changes after the method was recorded, the recording is lost.

Example:
Map<Object, Integer> myMap = new Map<Object, Integer>();
Account a = new Account(Name = 'foo');
myMap.put(a, 1);
System.debug(myMap);
// 17:39:27.13 (14714888)|USER_DEBUG|[4]|DEBUG|{Account:{Name=foo}=1}
a.Name = 'bar';
System.debug(myMap);
// 17:39:27.13 (14826544)|USER_DEBUG|[6]|DEBUG|{Account:{Name=bar}=null}

Unfortunately, APEX doesn't provide a native method for deepClone of Object (not that I'm aware of at least). If it did, the map could contain a clone of the MethodArg and problem solved. This becomes a challenge when a Method A would be called with Object State 1, then Object State transitioned to 2 and Method B called. In this case, A would not verify because of the above situation.

Solutions that I can think of:

  1. Don't ever modify object after calling a method that is being tracked - Not really viable
  2. Clone the method arg prior to calling method recorder - This becomes a challenge with the exception of List which provides deepClone. For other classes, a clone method would need to be written. If there was a "clone", method param could be cloned prior to adding to List passed to method recorder.
  3. Instead of using Map<Object, Integer>, approach method arg detection in a different way

Does anyone else see this as an issue? Thoughts?

Update - One other area that this could present a problem is with Dates and DateTimes. For example, if a method uses DateTime.now, to build an object to pass in to verify with exactly the same time would require using a static member (e.g. Utils.CurrentDateTime) that gets set to DateTime.now instead of just liberally calling DateTime.now when the current DateTime is needed. Using a static member such as this that both the test method and service methods call would ensure the datetime is always the same but this does raise another concern regarding how methods are recorded/tracked/verified.

Spring 16 causing tests to fail

fflib_ApexMocksTest class is currently failing on orgs that are running Spring '16.
As of 6 Feb 2016 EU5 has been updated and we are getting several failed asserts from the library. On EU0, EU1, EU2 and EU3, which have not yet been updated to newest release, the tests run smoothly.

As we are not using the mocks extensively, we plan to comment out the test class so we carry on with new release of our managed package.

TEST RESULTS:

METHOD RESULT
whenStubExceptionTheExceptionShouldBeThrown : Fail
STACK TRACE
Class.KaptioTravel.fflib_ApexMocksTest.whenStubExceptionTheExceptionShouldBeThrown: line 222, column 1
MESSAGE
System.AssertException: Assertion Failed

METHOD RESULT
whenStubMultipleVoidMethodsWithExceptionsThenExceptionsShouldBeThrown : Fail
STACK TRACE
Class.KaptioTravel.fflib_ApexMocksTest.whenStubMultipleVoidMethodsWithExceptionsThenExceptionsShouldBeThrown: line 268, column 1
MESSAGE
System.AssertException: Assertion Failed: Stubbed exception should have been thrown.

METHOD RESULT
whenStubVoidMethodWithExceptionAndCallMethodTwiceThenExceptionShouldBeThrownTwice : Fail
STACK TRACE
Class.KaptioTravel.fflib_ApexMocksTest.whenStubVoidMethodWithExceptionAndCallMethodTwiceThenExceptionShouldBeThrownTwice: line 306, column 1
MESSAGE
System.AssertException: Assertion Failed: Stubbed exception should have been thrown.

METHOD RESULT
whenStubVoidMethodWithExceptionThenExceptionShouldBeThrown : Fail
STACK TRACE
Class.KaptioTravel.fflib_ApexMocksTest.whenStubVoidMethodWithExceptionThenExceptionShouldBeThrown: line 242, column 1
MESSAGE
System.AssertException: Assertion Failed: Stubbed exception should have been thrown.

apex-mocks-generator skips the line after the interface definition

The title explains it all really, but here are some examples which show how this actually affects things in practice (note the positions of the curly braces):

public interface MyInterface
{
     void foo();
     void bar();
}

Generates:

public class MockMyInterface implements MyInterface
{
    private fflib_ApexMocks mocks;

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

    public void foo()
    {
        mocks.mockVoidMethod(this, 'foo', new List<Object> {});
    }

    public void bar()
    {
        mocks.mockVoidMethod(this, 'bar', new List<Object> {});
    }
}

This is correct.


public interface MyInterface {

     void foo();
     void bar();
}

Generates:

public class MockMyInterface implements MyInterface
{
    private fflib_ApexMocks mocks;

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

    public void foo()
    {
        mocks.mockVoidMethod(this, 'foo', new List<Object> {});
    }

    public void bar()
    {
        mocks.mockVoidMethod(this, 'bar', new List<Object> {});
    }
}

This is also correct.


However, as soon as I remove that blank line after the method definition:

public interface MyInterface {
     void foo();
     void bar();
}

Generates:

public class MockMyInterface implements MyInterface
{
    private fflib_ApexMocks mocks;

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

    public void bar()
    {
        mocks.mockVoidMethod(this, 'bar', new List<Object> {});
    }
}

This is not correct. Notice that foo() has gone missing.

Deploy to Salesforce Button Does not Work

When deploying with "Deploy to Salesforce" button, this error occurs:

Deployment Started
Status: Queued
Status: InProgress
Status: Completed
Deployment Complete
Failures:
customMetadata/README.md(1,1):Error parsing file: Content is not allowed in prolog.

Improper mock class generated when extends on separate line

If an interface contains and extends clause and the clause is on a different line from the interface name, the generated mock class is poorly formed.

Below are a couple examples. Given the different placement of the extends clause, the generated class should be well formed and logically identical.

Given the following interface, having the extends clause on the same line as the interface name.

public interface ICronTriggersSelector extends ISObjectSelector
{
    List<CronTrigger> selectById(Set<Id> idSet);
}

The proper mock class is generated.

/* Generated by apex-mocks-generator version 4.0.1 */
@isTest
public class MockClasses
{
    public class CronTriggersSelectorMock extends ISObjectSelector implements ICronTriggersSelector
    {
        private fflib_ApexMocks mocks;

        public CronTriggersSelectorMock(fflib_ApexMocks mocks)
        {
            super(mocks);
            this.mocks = mocks;
        }

        public List<CronTrigger> selectById(Set<Id> idSet)
        {
            return (List<CronTrigger>) mocks.mockNonVoidMethod(this, 'selectById', new List<Type> {System.Type.forName('Set<Id>')}, new List<Object> {idSet});
        }
    }
}

But when the interface's extends clause is moved to the following line.

public interface ICronTriggersSelector
    extends ISObjectSelector
{
    List<CronTrigger> selectById(Set<Id> idSet);
}

Notice the improper code that appears after the mock class's constructor.

/* Generated by apex-mocks-generator version 4.0.1 */
@isTest
public class MockClasses
{
    public class CronTriggersSelectorMock extends ISObjectSelector implements ICronTriggersSelector
    {
        private fflib_ApexMocks mocks;

        public CronTriggersSelectorMock(fflib_ApexMocks mocks)
        {
            super(mocks);
            this.mocks = mocks;
        }

        public extends ISObjectSelector{    List<CronTrigger> selectById(Set<Id> idSet)
        {
            return (extends ISObjectSelector{    List<CronTrigger>) mocks.mockNonVoidMethod(this, 'selectById', new List<Type> {System.Type.forName('Set<Id>')}, new List<Object> {idSet});
        }
    }
}

How to: ApexMocks - dependency on uow.commitWork() ID creation

I'll be the first to admit I'm no expert in ApexMocks but do understand fflib_ApexCommon quite well

Class/Method under test - inserts an SObject and then uses the inserted SObject's ID to call an email service. Example is simplified from real work requirement

public class Foo {
  public void doWork() {
     fflib_ISobjectUnitOfWork uow = Application.UnitOfWork.newInstance();
     Account a = new Account(Name='A0', Website = 'www.salesforce.com');
     uow.registerNew(a); 
     uow.commitWork();
     EmailService.sendEmail(a.Id);  // relies on commitWork inserting the Account
  }
}

TestMethod

private class TestApexMocks {
   @isTest private static void testFoo() {
     fflib_ApexMocks mocks = new fflib_ApexMocks();
     // Given mock implementation of UnitOfWork
     fflib_SobjectUnitOfWork mockUow    = (fflib_SobjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class);
     Application.UnitOfWork.setMock(mockUow);

    // Given mock implementation of EmailService
    EmailServiceImpl mockEmailSvc = (EmailServiceImpl) mocks.mock(EmailServiceImpl.class);
    Application.Service.setMock(IEmailService.class,mockEmailSvc);

     // When method invoked
     new Foo().doWork();

    // Then verify EmailService called with inserted Account ID - How??
     ((EmailServiceImpl) mocks.verify(mockEmailSvc,mocks.times(1)))
                      .sendEmail(??);

}

Essentially the issue is that uow.commitWork() is a void method so thenReturn doesn't help. uow.commitWork() has a side effect, new SObjects get IDs - but if you are mocking uow, how does one mock that side effect so the code under test gets that ID in variable a?

The meta comment here is that unit testing methods that use the Unit of Work layer is difficult when the code relies on the values of inserted sobjects to do further work such as in my EmailService example, or calling an async method like future or queueable. AFAIK, you can't verify the payloads to these follow-on services if those payloads include IDs from the committed new SObjects.

I remain tantalized by ApexMocks to make my tests faster and easier to set up but figuring out how to verify when no real DML is being done by the testmethod is perplexing/challenging.

Generator tool does not generate all methods when interface extends another interface

If I have an interface that extends another interface the mock generator tool only generates the methods explicitly within the sub interface for the generated mock class and does not generate methods declared in the extended super interface.

For example if I have the following two interfaces:

public interface IBaseInterface {
void myBaseMethod();
}

public interface ISuperInterface extends IBaseInterface {
void mySuperMethod();
}

Then the generated mock class for ISuperInterface only implements the mySuperMethod() method.

How to use matcher to see if set contains or map containsKey or map (for key) has value?

Looking through the examples in fflib_Match.cls and fflib_ApexMocksTest.cls I'm not seeing any way to

  • see if a set contains an entry
  • see if a map contains a key
  • see if a map for key k, has value v

My example is testing a VF controller that calls a FooService.doBar(..) Hence, mocking a service FooServiceImpl, method doBar(map<ID,Foo.CustomType>)

I tried this (match on entire map)

// preamble to set up mocks and inject not shown ; it works fine
((FooServiceImpl) mocks.verify(mockFooSvc,1))
  .doBar(new map<ID,Foo.CustomType> {someId => new Foo.CustomType(arg1, arg2)});

but even though doBar is called with the map someId => new Foo.CustomType(arg1, arg2) (verified using debug), the verify method in ApexMocks comes back with

Expected : 1, Actual: 0 -- Wanted but not invoked: FooServiceImpl__sfdc_ApexStub.doBar(Map<Id,Foo.CustomType>)

So, bottom line - would be nice to see how to use matchers on sets (specific entry) and maps (entire map, hasKey, hasValueForKey, etc.) - especially without having to resort to custom matchers.

fflib_ApexMocksUtils.makeRelationship does not support some standard objects?

I am experiencing a problem when try to make relationship with two standard objects Folder and Document.

Below the code I am using:

    List<Folder> folders = new List<Folder>{
        new Folder(
            Id = fflib_IDGenerator.generate(Folder.SObjectType),
            Name = 'Test Public Folder'
    )};
    List<Document> allDocuments = new List<Document>{
        new Document(
            Id = fflib_IdGenerator.generate(Document.SObjectType),
            Name = 'Test Document',
            Type = 'pdf',
            FolderId = folders[0].Id
    )};
    folders = (List<Folder>) fflib_ApexMocksUtils.makeRelationship(
        List<Folder>.class, 
        folders, 
        Document.FolderId, 
        new List<List<Document>> {allDocuments}
    );

The exception I get is:

    12:56:40:000 FATAL_ERROR Class.System.JSONGenerator.writeFieldName: line 58, column 1
    12:56:40:000 FATAL_ERROR Class.fflib_ApexMocksUtils.InjectChildrenEventHandler.nextToken: line 113, column 1
    12:56:40:000 FATAL_ERROR Class.fflib_ApexMocksUtils.streamTokens: line 135, column 1
    12:56:40:000 FATAL_ERROR Class.fflib_ApexMocksUtils.makeRelationship: line 85, column 1

thenReturn(null) not supported?

I noticed that if I try to program my mock to return a null value, like

when(theMock.someMethod('someValue')).thenReturn(null)

I get a
System.NullPointerException: Attempt to de-reference a null object Class.fflib_ApexMocks.mockNonVoidMethod: line 246, column 1

Count recorder counting calls across type rather than per mock instance

When I have multiple mocks of a specific type in my unit test, and I want to verify calls on each of the individual mocks, the call counts for each individual mock instance are incorrect as they are the sum of calls across all the mock instances of that type. Looking at the fflib_ApexMocks and fflibMethodCountRecorder classes I can see the call recordings are aggregated on a per type basis rather than recording calls per instance.

Of course I can get round this by creating separate fflib_ApexMocks instances and having the separate mocks I wish to verify counts on use their own individual fflib_ApexMocks instance but the behaviour of aggregating call counts on a per type basis rather than counting calls separately for the individual mock object instances seems wrong.

Functionality for mocking method call to alter mutable parameters

Hi there,
I have an interface with a method that applies changes to mutable parameters and as far as I can tell the fflib library does not provide the functionality to setup the mock instance in the unit test to apply changes to the input parameters.

If this is indeed not currently available, are there any plans to add this in future? It would be a very useful addition to the library.

Cheers,
Donnie

MatcherDefinitions aren't correct for v37.0

To make the following assertions (Which are currently in fflib_MatcherDefinitionsTest) run correctly:

    @isTest
    private static void whenIsBlankWithMatchesShouldReturnCorrectResults()
    {
        fflib_IMatcher matcher = new fflib_MatcherDefinitions.StringIsBlank();
        System.assert(!matcher.matches(7));
        System.assert(!matcher.matches('bob'));
        System.assert(matcher.matches(null));
        System.assert(matcher.matches(''));
    }

    @isTest
    private static void whenIsNotBlankWithMatchesShouldReturnCorrectResults()
    {
        fflib_IMatcher matcher = new fflib_MatcherDefinitions.StringIsNotBlank();
        System.assert(!matcher.matches(7));
        System.assert(!matcher.matches(null));
        System.assert(!matcher.matches(''));
        System.assert(matcher.matches('bob'));
    }

The matchers must be as follows:

       /**
     * StringIsBlank matcher: checks if the supplied argument is a blank String
     */
    public class StringIsBlank implements fflib_IMatcher
    {       
        public Boolean matches(Object arg)
        {
            return arg == NULL || (arg instanceof String ? String.isBlank((String)arg) : false);
        }
    }

    /**
     * StringIsNotBlank matcher: checks if the supplied argument is a non-blank string
     */
    public class StringIsNotBlank implements fflib_IMatcher
    {       
        public Boolean matches(Object arg)
        {
            return arg instanceof String ? !String.isBlank((String)arg) : false;
        }
    }

Add support for ANY parameter

Currently, method arguments must match exactly in order to be verified or have the proper result return. Often times, the need arises to verify a method is called 1 or more times regardless of input or have a value returned regardless of input.

Would be nice to be able to setup a mock method that will accept any argument(s) and/or verify a method was called N times without requiring an exact match on the method arguments.

Setup Example:
mocks.when(myMock.myMethod(fflib_ApexMocks.ANY, fflib_ApexMocks.ANY)).thenReturn(5);

Verification Example:
((IMyInterface)mocks.verify(myMock, 3).myMethod(fflib_ApexMocks.ANY, fflib_ApexMocks.ANY)

Variable does not exist: Application.Service

I have the following test:
` @istest
static void MockWhenHasNoOpportunitiesResponseSHouldBeFalse()
{
fflib_ApexMocks mocks = new fflib_ApexMocks();
AlexMockExample2 mockEg2Object = (AlexMockExample2)mocks.factory(AlexMockExample2.class);

    mocks.startStubbing();
    mocks.when(mockEg2Object.GetNumberOfOpportunities()).thenReturn(0);
    mocks.stopStubbing();
    
    // Errors on this line.
    Application.Service.setMock(mockEg2Object.API.class, mockEg2Object);


    AlexMockExample1 eg1Object = new AlexMockExample1();
    
    Test.startTest();
    Boolean response = eg1Object.HasOpportunities();
    Test.stopTest();
    
    
    System.assertEquals(false, response);
    
    
}`

But I get the following errors ::
Variable does not exist: Application.Service and Invalid type: mockEg2Object.API.

Commenting the line does not return the correct Integer defined in the stub.

I pushed all the classes into my sandbox. Do I need to do something with the .jar files?

What am I doing wrong here?

Thanks
Alex

Matchers not cleared when verifying method was not called

I'll write a test for this as soon as I can but believe there is an issue in fflib_MethodCountRecorder::getMethodCount.

If a method is verified to not have been called (e.g. verify(myMock, 0)) and the method was not called (as expected), the matchers are not being cleared. Therefore, on the next verify, the following exception occurs:

fflib_ApexMocks.ApexMocksException: The number of matchers defined (6). does not match the number expected (3)
If you are using matchers all arguments must be passed in as matchers.
For example myList.add(fflib_Match.anyInteger(), 'String') should be defined as myList.add(fflib_Match.anyInteger(), fflib_Match.eq('String')).

I haven't had time to dig in to how the new matcher logic behaves but it appears that adding an "else if" to clear the matchers resolves the issue - not sure it's the correct solution though. Apologize for not having a test for this, will write one as soon as I can.

if (methodCountByArgs != null)
{
    if (fflib_Match.Matching)
    {
        List<fflib_IMatcher> matchers = fflib_Match.getAndClearMatchers(methodArg.argValues.size());

        for (fflib_MethodArgValues args : methodCountByArgs.keySet())
        {
            if (fflib_Match.matchesAllArgs(args, matchers))
            {
                retval += methodCountByArgs.get(args);
            }
        }
    }
    else
    {
        if (methodCountByArgs.get(methodArg) != null)
        {
            return methodCountByArgs.get(methodArg);
        }
    }
} else if (fflib_Match.Matching) {
    fflib_Match.getAndClearMatchers(methodArg.argValues.size());
}

Thoughts?

GACK in pod cs62 - run tests for fflib_ApexMocks

Almost every fflib ApexMocks testclass fails (but only in pod cs62 spring 17). Runs fine in cs44 (both spring 17 and summer 17 preview)

System.UnexpectedException: Salesforce System Error: 941665292-18943 (327514294) (327514294)

The tests all fail in exactly the same place on line 67 of fflib_ApexMocks.mock line 67 calling Apex system class System.Test.createStub. Here's an example stack trace:

Class.System.Test.createStub: line 93, column 1
Class.fflib_ApexMocks.mock: line 67, column 1
Class.fflib_ApexMocksTest.whenVerifyMethodNeverCalledMatchersAreReset: line 703, column 1

Line 67 is:

return Test.createStub(classToMock, this);

Seems pretty basic functionality

Test classes that fail:
fflib_AnswerTest (every method)
fflib_AnyOrderTest (every method)
fflib_ApexMocksTest (every method)
fflib_ArgumentCaptorTest (every method)
fflib_InOrderTest (every method)
fflib_InheritorTest (every method)

fflib was installed into three different sandbox orgs (for the same PROD org) today within the span of one hour; run all fflib_XXX tests failed only in pod cs62; other org/pods work fine

Unfortunately, we don't have Premier Support in this org so getting SFDC assistance as to the gack origin isn't going to be easy. And - since it seems to be pod-dependent, we're kind of stuck. The failing org is our staging org so we can't easily/practically refresh it and hope for a 'better pod'

GACK implies platform bug - I don't see an obvious way to work around this.

1 - Can I delete all the ApexMocks classes from the org and just use apex-common? Curiously, apex-common tests all pass so if they are using ApexMocks methods for tests, then why would the ApexMocks testmethods gack?

Simulate Mockito.spy behavior?

Hi Guys,

I was wondering if there's a way of testing the following scenario:

class A {

  void go() {
      Obj o1 = do1();
      Obj2 o2 =  do2(o1);
      do3(o2);
  }

  public Obj do1() {
    // a lot of complexity in here
  }

  public Obj2 do2() {
    // a lot of complexity in here
  }

  public void do3() {
    // a lot of complexity in here
  }
}

Then I would like to be able to mock do1, do2 and do3 when I'm testing against go(), and of course, I'll have separated tests for do1, do2 and do3.

With mockito I could achieve this by:

A a = Mockito.spy(new A());
// stubs and so on..
a.go();
verify(a, times(1)).do1();

I've tried to look at the implementation but I didn't have any insight. Thanks guys!

Verify method throwing exception when using doAnswer

I am trying to test my controller method. But mocks.verify is throwing an exception "fflib_ApexMocks.ApexMocksException: Expected : 1, Actual: 0 -- Wanted but not invoked: "

Below is the code I have written. I wrote similar code in a different method where it is working fine. The only difference is that I am passing a list of sobjects than a list of wrapper objects.

Can you please let me know if there is any issue in passing wrapper objects to the doAnswer?
I am trying to mock a service method that has void return type.
`

//Controller method
public static void insertContractAndSeasonContracts(Contract__c contract, List<Id> selectedSeasonIds) {
    ContractsService.ContractSeasonWrapper contractsSeasons = new ContractsService.ContractSeasonWrapper(
        contract, selectedSeasonIds, null
    );
    IContractsService contractsService = ContractsService.newInstance();
    
    contractsService.insertContractAndSeasonContracts(
        new List<ContractsService.ContractSeasonWrapper>{contractSeasons}
    );
}

public class ContractSeasonWrapper {
    public Contract__c contract;
    public List<Id> selectedSeasonIds;
    public List<Id> existingSeasonIds;
    public ContractSeasonWrapper(Contract__c contract, List<Id> selectedSeasonIds, List<Id> existingSeasonIds) {
        this.contract = contract;
        this.selectedSeasonIds = selectedSeasonIds;
        this.existingSeasonIds = existingSeasonIds;
    }
}

//Test method
static void testInsertContractAndSeasonContracts() {
    fflib_ApexMocks mocks= new fflib_ApexMocks();
    ContractsService contractsServiceMock = (ContractsService) mocks.mock(
        ContractsService.class
    );

    Contract__c newContract = new Contract__c(
        Name = 'test Contract'
    );
    List<Id> selectedSeasonIds = new List<Id>{fflib_IDGenerator.generate(Season__c.SObjectType)};

    ContractsService.ContractSeasonWrapper contractSeasonWrapper = new ContractsService.ContractSeasonWrapper(
        newContract, selectedSeasonIds, null
    );
    
    List<ContractsService.ContractSeasonWrapper> contractSeasonWrappers = 
        new List<ContractsService.ContractSeasonWrapper>{contractSeasonWrapper};


    mocks.startStubbing();
    ((IContractsService) mocks.doAnswer(
        new ContractServiceAnswer(contractSeasonWrappers), contractsServiceMock))
        .insertContractAndSeasonContracts(contractSeasonWrappers);
    mocks.stopStubbing();

    Application.Service.setMock(IContractsService.class, contractsServiceMock);

    ContractController.insertContractAndSeasonContracts(newContract, selectedSeasonIds);
    System.debug(mocks.verify(contractsServiceMock));
    ((IContractsService) mocks.verify(contractsServiceMock)).insertContractAndSeasonContracts(
        new List<ContractsService.ContractSeasonWrapper>{contractSeasonWrapper}
    );
}

`

recordMethod() in fflib_MethodCountRecorder fails if methodArgs contains a mock instance with equals(Object o) method overridden

When an fflib generated mock class implements an interface with equals/hashcode, there is an issue with recording calls to equals with an argument being an instance of such a mock type.

In this scenario the methodCountByArgs.get(methodArg) call within the fflib_MethodCountRecorder class' recordMethod() method calls the overridden equals method in fflib_MethodArgValues. This in turn then calls the mock instance's equals method which then results in a call back to the fflib_MethodCountRecorder recordMethod() method once again; we then go back through the same method calls as before and end up in an infinite cycle of method calls cycling between the fflib_MethodArgValues equals() and the mock class' equals() methods (till an exception eventually gets thrown).

Perhaps the fflib_MethodArgValues class could do something clever to wrap mock objects and have wrapper implementing equals using referential === for mocked instances to avoid this problem (would need the code generator to make the generated mock classes implement some no-method fflib interface to identify the instances as being an fflib generated mock class for this I imagine).

Mock Generator: Invalid conversion from runtime type

I finally got the 4.0.1 generator to work today (first crack at setting up ApexMocks. As expected the generated populated my Mocks.cls class.

**Note: ** One thing to mention about this is I put ApexMocks into a managed package with a namespace because as a 3rd party tool I'd rather manage it like such. The generator ignores my package namespace so I have to manually add it to each property prior to deploying it

With that in place I'm attempting my first unit test with the following:

@IsTest
private class AccountsSelectorTest {

	@IsTest
	private static void selectById() {
		// Create mocks
		MyNamespace.fflib_ApexMocks mocks = new MyNamespace.fflib_ApexMocks();
		IAccountsSelector selectorMock = new Mocks.AccountsSelector(mocks);

		// Create our account but don't insert it into the database
		Account a = new Account(
			Id = MyNamespace.fflib_IDGenerator.generate(Account.SObjectType),
			Name = 'Test Account'
		);
		
		// Given
		mocks.startStubbing();
		// Use ApexMocks to stub the return value for the service's selectById() method
		mocks.when(selectorMock.selectById(new Set<Id> { a.Id })).thenReturn(a);
		mocks.stopStubbing();

		// When
		List<Account> accounts = selectorMock.selectById(new Set<Id> { a.Id });

		// Assert
		System.assertEquals('Test Account', accounts[0].Name);
	}

}

Unfortunately this test fails because of a fatal error coming from the generated mock class (Mocks.cls):

public class AccountsSelector extends SObjectMocks.SObjectSelector implements IAccountsSelector
{
	private AgasMocks.fflib_ApexMocks mocks;

	public AccountsSelector(AgasMocks.fflib_ApexMocks mocks)
	{
		super(mocks);
		this.mocks = mocks;
	}

	public List<Account> selectById(Set<ID> idSet)
	{
>>>ERROR>>>	return (List<Account>) mocks.mockNonVoidMethod(this, 'selectById', new List<Type> {System.Type.forName('Set<ID>')}, new List<Object> {idSet});
	}
}

Sidebar: Is there a way to change the style guide used by the generator, would really like to see things like open brackets in their proper place ;)

19:29:11:023 FATAL_ERROR System.TypeException: Invalid conversion from runtime type Account to List

My Selector class method is setup just like the OpportunitiesSelector.cls example here.

Is this an issue because of the managed package? Or from what I can tell it has something to do with the fact that the fflib_ApexMocks.cls.mockNonVoidMethod() only expects an Object in return and can't handle a List<Object>?

Appreciate the help and the framework the more I'm understanding it all, so thank you!

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.