apex-enterprise-patterns / fflib-apex-mocks Goto Github PK
View Code? Open in Web Editor NEWAn Apex mocking framework for true unit testing in Salesforce, with Stub API support
License: BSD 3-Clause "New" or "Revised" License
An Apex mocking framework for true unit testing in Salesforce, with Stub API support
License: BSD 3-Clause "New" or "Revised" License
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.
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)
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);
}
}
public with sharing class StuffServiceImpl implements IStuffService {
static List<id> ids = new List<Id>();
public void add(List<Id> idsToAdd) {
ids.addAll(idsToAdd);
}
}
@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.
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.
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
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}
);
}
`
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;
}
}
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.
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:
Hence ...
Options
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)toMatch
's implicit SObjectType with soArg.getSobjectType()
and if different, return false and avoid throwing the SObjectExceptions.The Apex classes in this repo appear to be at version 37 -- let's update to 48
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)
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).
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:
All in all these changes remove approx. 50% of the necessary mocking code, which I think is really a lot.
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 |
Inside a testMethod
, we loop through a list of TestCase
s, 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.
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),
};
}
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.
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
factory()
method instead of mock()
method) andThus, getting the fflib / andyinthecloud online doc up to date / more comprehensive would go a long way towards increased adoption.
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
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.
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?
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
When mocking a Void method that throws an exception, it appears that the method will not be recorded since the exception is thrown before recording the method call.
Should line 214 move to line 207?
https://github.com/financialforcedev/fflib-apex-mocks/blob/master/src/classes/fflib_ApexMocks.cls#L214
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
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:
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.
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)
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.
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.
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?
Foo__c
a mismatch?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.
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
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.
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?
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!
customMetadata/README.md(1,1):Error parsing file: Content is not allowed in prolog.
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.
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.
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?
fflib_MatcherDefinitionsTest.whenIsBlankWithMatchesShouldReturnCorrectResults() has an assertion around matching a null parameter. It fails for me.
Came across a need to generate subquery data today and came across this gem by Andrew from 2014 - http://andyinthecloud.com/2014/12/03/mocking-soql-sub-select-query-results/.
Not sure if the proper home for something like this is Apex Mocks or the upcoming test builder repo? I can see merit in both places. Thoughts?
Looking through the examples in fflib_Match.cls
and fflib_ApexMocksTest.cls
I'm not seeing any way to
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.
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});
}
}
}
Need to put in a new solution for CI builds
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.
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!
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);
my production org is complaining about the test coverage for fflib_QualifiedMethodAndArgValues am I missing something?
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?
This class uses the isTest notation and appears when running tests but is placed in the main/classes folder. @ImJohnMDaniel
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.
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.
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();
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.