diff --git a/sfdx-source/apex-mocks/fflib_Answer.cls b/sfdx-source/apex-mocks/fflib_Answer.cls
new file mode 100644
index 00000000000..874e6f80c81
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_Answer.cls
@@ -0,0 +1,19 @@
+/*
+ Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * Interface for the answering framework.
+ * This interface must be implemented inside the test class and implement the call back method answer.
+ * @group Core
+ */
+public interface fflib_Answer
+{
+ /**
+ * Method to be implemented in the test class to implement the call back method.
+ * @param invocation The invocation on the mock.
+ * @throws The exception to be thrown.
+ * @return The value to be returned.
+ */
+ Object answer(fflib_InvocationOnMock invocation);
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_Answer.cls-meta.xml b/sfdx-source/apex-mocks/fflib_Answer.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_Answer.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_AnyOrder.cls b/sfdx-source/apex-mocks/fflib_AnyOrder.cls
new file mode 100644
index 00000000000..07826c393e4
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_AnyOrder.cls
@@ -0,0 +1,105 @@
+/*
+ Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * 'Classic' invocation verifier - checks that a method was called with the given arguments the expected number of times.
+ * The order of method calls is not important.
+ * @group Core
+ */
+public class fflib_AnyOrder extends fflib_MethodVerifier
+{
+ /*
+ * Verifies a method was invoked the expected number of times, with the expected arguments.
+ * @param qualifiedMethod The method to be verified.
+ * @param methodArg The arguments of the method that needs to be verified.
+ * @param verificationMode The verification mode that holds the setting about how the verification should be performed.
+ */
+ protected override void verify(
+ fflib_QualifiedMethod qm,
+ fflib_MethodArgValues expectedArguments,
+ fflib_VerificationMode verificationMode)
+ {
+ List expectedMatchers = fflib_Match.Matching ? fflib_Match.getAndClearMatchers(expectedArguments.argValues.size()) : null;
+ List actualArguments = fflib_MethodCountRecorder.getMethodArgumentsByTypeName().get(qm);
+
+ Integer methodCount = getMethodCount(expectedArguments, expectedMatchers, actualArguments);
+
+ String qualifier = '';
+ Integer expectedCount = null;
+
+ if((verificationMode.VerifyMin == verificationMode.VerifyMax) && methodCount != verificationMode.VerifyMin)
+ {
+ expectedCount = verificationMode.VerifyMin;
+ }
+ else if (verificationMode.VerifyMin != null && verificationMode.VerifyMin > methodCount)
+ {
+ expectedCount = verificationMode.VerifyMin;
+ qualifier = ' or more times';
+ }
+ else if (verificationMode.VerifyMax != null && verificationMode.VerifyMax < methodCount)
+ {
+ expectedCount = verificationMode.VerifyMax;
+ qualifier = ' or fewer times';
+ }
+
+ if (expectedCount != null)
+ {
+ throwException(qm, '', expectedCount, qualifier, methodCount, verificationMode.CustomAssertMessage, expectedArguments, expectedMatchers, actualArguments);
+ }
+ }
+
+ private Integer getMethodCount(fflib_MethodArgValues methodArg, List matchers, List methodArgs)
+ {
+ Integer retval = 0;
+
+ if (methodArgs != null)
+ {
+ if (matchers != null)
+ {
+ for (fflib_MethodArgValues args : methodArgs)
+ {
+ if (fflib_Match.matchesAllArgs(args, matchers))
+ {
+ capture(matchers);
+ retval ++;
+ }
+ }
+ }
+ else
+ {
+ return countCalls(methodArgs, methodArg);
+ }
+ }
+
+ return retval;
+ }
+
+ private Integer countCalls(List methodArgs, fflib_MethodArgValues methodArg)
+ {
+ Integer count = 0;
+
+ for(fflib_MethodArgValues arg: methodArgs)
+ {
+ if( arg == methodArg) count++;
+ }
+
+ return count;
+ }
+
+ /*
+ * Method that validate the verification mode used in the verify.
+ * Not all the methods from the fflib_VerificationMode are implemented for the different classes that extends the fflib_MethodVerifier.
+ * The error is thrown at run time, so this method is called in the method that actually performs the verify.
+ * @param verificationMode The verification mode that have to been verified.
+ * @throws Exception with message for the fflib_VerificationMode not implemented.
+ */
+ protected override void validateMode(fflib_VerificationMode verificationMode)
+ {
+ if(verificationMode.Method == fflib_VerificationMode.ModeName.CALLS)
+ {
+ throw new fflib_ApexMocks.ApexMocksException(
+ 'The calls() method is available only in the InOrder Verification.');
+ }
+ }
+}
diff --git a/sfdx-source/apex-mocks/fflib_AnyOrder.cls-meta.xml b/sfdx-source/apex-mocks/fflib_AnyOrder.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_AnyOrder.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_ApexMocks.cls b/sfdx-source/apex-mocks/fflib_ApexMocks.cls
new file mode 100644
index 00000000000..be18f4aeafe
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_ApexMocks.cls
@@ -0,0 +1,432 @@
+/*
+ Copyright (c) 2014-2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * @group Core
+ */
+public with sharing class fflib_ApexMocks implements System.StubProvider
+{
+ public static final Integer NEVER = 0;
+
+ private final fflib_MethodCountRecorder methodCountRecorder;
+ private final fflib_MethodReturnValueRecorder methodReturnValueRecorder;
+
+ private fflib_MethodVerifier methodVerifier;
+ private fflib_VerificationMode verificationMode;
+ private fflib_Answer myAnswer;
+
+ public Boolean verifying { get; set; }
+
+ public Boolean Stubbing
+ {
+ get
+ {
+ return methodReturnValueRecorder.Stubbing;
+ }
+
+ private set;
+ }
+
+ public List DoThrowWhenExceptions
+ {
+ get
+ {
+ return methodReturnValueRecorder.DoThrowWhenExceptions;
+ }
+
+ set
+ {
+ methodReturnValueRecorder.DoThrowWhenExceptions = value;
+ }
+ }
+
+ /**
+ * Construct an ApexMocks instance.
+ */
+ public fflib_ApexMocks()
+ {
+ this.verifying = false;
+
+ this.methodCountRecorder = new fflib_MethodCountRecorder();
+ this.verificationMode = new fflib_VerificationMode();
+ this.methodVerifier = new fflib_AnyOrder();
+
+ this.methodReturnValueRecorder = new fflib_MethodReturnValueRecorder();
+
+ this.methodReturnValueRecorder.Stubbing = false;
+ }
+
+ /**
+ * Creates mock object of given class or interface.
+ * @param classToMock class or interface to mock.
+ * @return mock object.
+ */
+ public Object mock(Type classToMock)
+ {
+ return Test.createStub(classToMock, this);
+ }
+
+ /**
+ * Inherited from StubProvider.
+ * @param stubbedObject The stubbed object.
+ * @param stubbedMethodName The name of the invoked method.
+ * @param returnType The return type of the invoked method.
+ * @param listOfParamTypes A list of the parameter types of the invoked method.
+ * @param listOfParamNames A list of the parameter names of the invoked method.
+ * @param listOfArgs The actual argument values passed into this method at runtime.
+ * @return The stubbed return value. Null by default, unless you prepared one that matches this method and argument values in stubbing.
+ */
+ public Object handleMethodCall(Object stubbedObject, String stubbedMethodName, Type returnType,
+ List listOfParamTypes, List listOfParamNames, List listOfArgs)
+ {
+ return mockNonVoidMethod(stubbedObject, stubbedMethodName, listOfParamTypes, listOfArgs);
+ }
+
+ public static String extractTypeName(Object mockInstance)
+ {
+ return String.valueOf(mockInstance).split(':').get(0);
+ }
+
+ /**
+ * Verify a method was called on a mock object.
+ * @param mockInstance The mock object instance.
+ * @return The mock object instance.
+ */
+ public Object verify(Object mockInstance)
+ {
+ return verify(mockInstance, this.times(1));
+ }
+
+ /**
+ * Verify a method was called on a mock object.
+ * @param mockInstance The mock object instance.
+ * @param verificationMode Defines the constraints for performing the verification (e.g. the minimum and maximum expected invocation counts).
+ * @return The mock object instance.
+ */
+ public Object verify(Object mockInstance, fflib_VerificationMode verificationMode)
+ {
+ this.verifying = true;
+ this.verificationMode = verificationMode;
+
+ return mockInstance;
+ }
+
+ /**
+ * Verify a method was called on a mock object.
+ * @param mockInstance The mock object instance.
+ * @param times The number of times you expect the method to have been called.
+ * @return The mock object instance.
+ */
+ public Object verify(Object mockInstance, Integer times)
+ {
+ return verify(mockInstance, this.times(times));
+ }
+
+ /**
+ * Verfiy a method was called on a mock object.
+ * @param mockInvocation The invocation on the mock containing information about the method and the arguments.
+ */
+ public void verifyMethodCall(fflib_InvocationOnMock mockInvocation)
+ {
+ this.methodVerifier.verifyMethodCall(mockInvocation, verificationMode);
+ this.methodVerifier = new fflib_AnyOrder();
+
+ this.verifying = false;
+ }
+
+ /**
+ * Tell ApexMocks framework you are about to start stubbing using when() calls.
+ */
+ public void startStubbing()
+ {
+ methodReturnValueRecorder.Stubbing = true;
+ }
+
+ /**
+ * Tell ApexMocks framework you are about to stop stubbing using when() calls.
+ */
+ public void stopStubbing()
+ {
+ methodReturnValueRecorder.Stubbing = false;
+ }
+
+ /**
+ * Setup when stubbing for a mock object instance.
+ * @param ignoredRetVal This is the return value from the method called on the mockInstance, and is ignored here since we are about to setup
+ * the stubbed return value using thenReturn() (see MethodReturnValue class below).
+ */
+ public fflib_MethodReturnValue when(Object ignoredRetVal)
+ {
+ return methodReturnValueRecorder.MethodReturnValue;
+ }
+
+ /**
+ * Record a method was called on a mock object.
+ * @param mockInvocation The invocation on the mock containing information about the method and the arguments.
+ */
+ public void recordMethod(fflib_InvocationOnMock mockInvocation)
+ {
+ methodCountRecorder.recordMethod(mockInvocation);
+ }
+
+ /**
+ * Prepare a stubbed method return value.
+ * @param mockInvocation The invocation on the mock containing information about the method and the arguments.
+ * @return The MethodReturnValue instance.
+ */
+ public fflib_MethodReturnValue prepareMethodReturnValue(fflib_InvocationOnMock mockInvocation)
+ {
+ return methodReturnValueRecorder.prepareMethodReturnValue(mockInvocation);
+ }
+
+ /**
+ * Get the method return value for the given method call.
+ * @param mockInvocation The invocation on the mock containing information about the method and the arguments.
+ * @return The MethodReturnValue instance.
+ */
+ public fflib_MethodReturnValue getMethodReturnValue(fflib_InvocationOnMock mockInvocation)
+ {
+ return methodReturnValueRecorder.getMethodReturnValue(mockInvocation);
+ }
+
+ /**
+ * Setup exception stubbing for a void method.
+ * @param e The exception to throw.
+ * @param mockInstance The mock object instance.
+ */
+ public Object doThrowWhen(Exception e, Object mockInstance)
+ {
+ methodReturnValueRecorder.prepareDoThrowWhenExceptions(new List{e});
+ return mockInstance;
+ }
+
+ /**
+ * Setup exception stubbing for a void method.
+ * @param exps The list of exceptions to throw.
+ * @param mockInstance The mock object instance.
+ */
+ public Object doThrowWhen(List exps, Object mockInstance)
+ {
+ methodReturnValueRecorder.prepareDoThrowWhenExceptions(exps);
+ return mockInstance;
+ }
+
+ /**
+ * Setup answer stubbing for a void method.
+ * @param answer The answer to invoke.
+ * @param mockInstance The mock object instance.
+ */
+ public Object doAnswer(fflib_Answer answer, Object mockInstance)
+ {
+ this.myAnswer = answer;
+ return mockInstance;
+ }
+
+ /**
+ * Mock a void method. Called by generated mock instance classes, not directly by a developers
+ * code.
+ * @param mockInstance The mock object instance.
+ * @param methodName The method for which to prepare a return value.
+ * @param methodArgTypes The method argument types for which to prepare a return value.
+ * @param methodArgValues The method argument values for which to prepare a return value.
+ */
+ public void mockVoidMethod(Object mockInstance, String methodName, List methodArgTypes, List methodArgValues)
+ {
+ mockNonVoidMethod(mockInstance, methodName, methodArgTypes, methodArgValues);
+ }
+
+ /**
+ * Mock a non-void method. Called by generated mock instance classes, not directly by a developers
+ * code.
+ * @param mockInstance The mock object instance.
+ * @param methodName The method for which to prepare a return value.
+ * @param methodArgTypes The method argument types for which to prepare a return value.
+ * @param methodArgValues The method argument values for which to prepare a return value.
+ */
+ public Object mockNonVoidMethod(Object mockInstance, String methodName, List methodArgTypes, List methodArgValues)
+ {
+ fflib_QualifiedMethod qm = new fflib_QualifiedMethod(extractTypeName(mockInstance), methodName, methodArgTypes, mockInstance);
+ fflib_MethodArgValues argValues = new fflib_MethodArgValues(methodArgValues);
+
+ fflib_InvocationOnMock invocation = new fflib_InvocationOnMock(qm, argValues, mockInstance);
+
+ if (this.verifying)
+ {
+ verifyMethodCall(invocation);
+ }
+ else if (Stubbing)
+ {
+ fflib_MethodReturnValue methotReturnValue = prepareMethodReturnValue(invocation);
+
+ if(DoThrowWhenExceptions != null)
+ {
+ methotReturnValue.thenThrowMulti(DoThrowWhenExceptions);
+ DoThrowWhenExceptions = null;
+ return null;
+ }
+
+ if(this.myAnswer != null)
+ {
+ methotReturnValue.thenAnswer(this.myAnswer);
+ this.myAnswer = null;
+ return null;
+ }
+
+ return null;
+ }
+ else
+ {
+ recordMethod(invocation);
+ return returnValue(invocation);
+ }
+
+ return null;
+ }
+
+ public class ApexMocksException extends Exception
+ {
+
+ }
+
+ private Object returnValue(fflib_InvocationOnMock invocation)
+ {
+ fflib_MethodReturnValue methodReturnValue = getMethodReturnValue(invocation);
+
+ if (methodReturnValue != null)
+ {
+ if(methodReturnValue.Answer == null)
+ {
+ throw new fflib_ApexMocks.ApexMocksException(
+ 'The stubbing is not correct, no return values have been set.');
+ }
+
+ Object returnedValue = methodReturnValue.Answer.answer(invocation);
+
+ if(returnedValue == null)
+ {
+ return null;
+ }
+
+ if (returnedValue instanceof Exception)
+ {
+ throw ((Exception) returnedValue);
+ }
+
+ return returnedValue;
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets how many times the method is expected to be called.
+ * For InOrder verification we copy Mockito behavior which is as follows;
+ *
+ * Consume the specified number of matching invocations, ignoring non-matching invocations in between
+ * Fail an assert if the very next invocation matches, but additional matches can still exist so long as at least one non-matching invocation exists before them
+ *
+ * For example if you had a(); a(); b(); a();
+ * then inOrder.verify(myMock, 2)).a(); or inOrder.verify(myMock, 3)).a(); would pass but not inOrder.verify(myMock, 1)).a();
+ * @param times The number of times you expect the method to have been called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode times(Integer times)
+ {
+ return new fflib_VerificationMode().times(times);
+ }
+
+ /**
+ * Sets how many times the method is expected to be called for an InOrder verifier. Available Only with the InOrder verification.
+ * A verification mode using calls will not fail if the method is called more times than expected.
+ * @param times The number of times you expect the method to have been called in the InOrder verifying ( no greedy verify).
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode calls(Integer times)
+ {
+ return new fflib_VerificationMode().calls(times);
+ }
+
+ /**
+ * Sets a custom assert message for the verify.
+ * @param customAssertMessage The custom message for the assert in case the assert is false. The custom message is queued to the default message.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode description(String customAssertMessage)
+ {
+ return new fflib_VerificationMode().description(customAssertMessage);
+ }
+
+ /**
+ * Sets the minimum number of times the method is expected to be called.
+ * With the InOrder verification it performs a greedy verification, which means it would consume all the instances of the method verified.
+ * @param atLeastTimes The minimum number of times you expect the method to have been called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode atLeast(Integer atLeastTimes)
+ {
+ return new fflib_VerificationMode().atLeast(atLeastTimes);
+ }
+
+ /**
+ * Sets the maximum number of times the method is expected to be called. Not available in the InOrder verification.
+ * @param atMostTimes The maximum number of times the method is expected to be called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode atMost(Integer atMostTimes)
+ {
+ return new fflib_VerificationMode().atMost(atMostTimes);
+ }
+
+ /**
+ * Sets that the method is called at least once.
+ * With the InOrder verification it performs a greedy verification, which means it would consume all the instances of the method verified.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode atLeastOnce()
+ {
+ return new fflib_VerificationMode().atLeastOnce();
+ }
+
+ /**
+ * Sets the range of how many times the method is expected to be called. Not available in the InOrder verification.
+ * @param atLeastTimes The minimum number of times you expect the method to have been called.
+ * @param atMostTimes The maximum number of times the method is expected to be called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode between(Integer atLeastTimes, Integer atMostTimes)
+ {
+ return new fflib_VerificationMode().between(atLeastTimes, atMostTimes);
+ }
+
+ /**
+ * Sets that the method is not expected to be called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode never()
+ {
+ return new fflib_VerificationMode().never();
+ }
+
+ /**
+ * Sets the fflib_VerificationMode object.
+ * To internal use only.
+ * Used to pass the verification mode that has been set in the verify of the fflib_InOrder class.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public void setOrderedVerifier(fflib_InOrder verifyOrderingMode)
+ {
+ this.methodVerifier = verifyOrderingMode;
+ }
+
+ /**
+ * A simple override to suppress the default toString logic, which is noisy and rarely useful.
+ * In some cases, the Salesforce default implementation of toString here can cause internal Salesforce errors
+ * if it has circular references.
+ * @return "fflib_ApexMocks" String literal
+ */
+ public override String toString()
+ {
+ return 'fflib_ApexMocks';
+ }
+}
diff --git a/sfdx-source/apex-mocks/fflib_ApexMocks.cls-meta.xml b/sfdx-source/apex-mocks/fflib_ApexMocks.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_ApexMocks.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_ApexMocksConfig.cls b/sfdx-source/apex-mocks/fflib_ApexMocksConfig.cls
new file mode 100644
index 00000000000..47d4d8a2bb5
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_ApexMocksConfig.cls
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+@IsTest
+public class fflib_ApexMocksConfig
+{
+ /**
+ * When false, stubbed behaviour and invocation counts are shared among all test spies.
+ * - See fflib_ApexMocksTest.thatMultipleInstancesCanBeMockedDependently
+ * - This is the default for backwards compatibility.
+ * When true, each test spy instance has its own stubbed behaviour and invocations.
+ * - See fflib_ApexMocksTest.thatMultipleInstancesCanBeMockedIndependently
+ */
+ public static Boolean HasIndependentMocks {get; set;}
+
+ static
+ {
+ HasIndependentMocks = false;
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_ApexMocksConfig.cls-meta.xml b/sfdx-source/apex-mocks/fflib_ApexMocksConfig.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_ApexMocksConfig.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_ApexMocksUtils.cls b/sfdx-source/apex-mocks/fflib_ApexMocksUtils.cls
new file mode 100644
index 00000000000..f24434b6912
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_ApexMocksUtils.cls
@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) 2014-2016, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public class fflib_ApexMocksUtils
+{
+ /**
+ * This is taken from https://gist.github.com/afawcett/8dbfc0e1d8c43c982881.
+ *
+ * This method works on the principle that serializing and deserialising child records is supported
+ *
+ * System.assertEquals(1, ((List)
+ * JSON.deserialize(
+ * JSON.serialize(
+ * [select Id, Name,
+ * (select Id, Name from Children__r) from Master__c]), List.class))
+ * [0].Children__r.size());
+ *
+ * This method results internally in constructing this JSON, before deserialising it back into SObject's
+ *
+ * [
+ * {
+ * "attributes": {
+ * "type": "Master__c",
+ * "url": "/services/data/v32.0/sobjects/Master__c/a0YG0000005Jn5uMAC"
+ * },
+ * "Name": "Fred",
+ * "Id": "a0YG0000005Jn5uMAC",
+ * "Children__r": {
+ * "totalSize": 1,
+ * "done": true,
+ * "records": [
+ * {
+ * "attributes": {
+ * "type": "Child__c",
+ * "url": "/services/data/v32.0/sobjects/Child__c/a0ZG0000006JGPAMA4"
+ * },
+ * "Name": "Bob",
+ * "Id": "a0ZG0000006JGPAMA4",
+ * "Master__c": "a0YG0000005Jn5uMAC"
+ * }
+ * ]
+ * }
+ * ]
+ */
+ public static Object makeRelationship(Type parentsType, List parents, SObjectField relationshipField, List> children) {
+
+ // Find out more about this relationship...
+ String relationshipFieldName = relationshipField.getDescribe().getName();
+ DescribeSObjectResult parentDescribe = parents.getSObjectType().getDescribe();
+ return deserializeParentsAndChildren(parentsType, parentDescribe, relationshipField, parents, children);
+ }
+
+ /**
+ * Generic overload to makeRelationship. Enables creation of
+ * relationships in a loosely-coupled manner.
+ */
+ public static Object makeRelationship(
+ String parentTypeName,
+ String childTypeName,
+ List parents,
+ String relationshipFieldName,
+ List> children) {
+
+ // Find out more about this relationship...
+ SObjectType parentType = getType(parentTypeName);
+ SObjectField relationshipField = getField(childTypeName, relationshipFieldName);
+ DescribeSObjectResult parentDescribe = parentType.getDescribe();
+ Type parentsType = List.class;
+ return deserializeParentsAndChildren(parentsType, parentDescribe, relationshipField, parents, children);
+ }
+
+ /**
+ * Gives the ability to set test values on formula
+ * and other read-only fields of mock SObjects
+ */
+ public static Object setReadOnlyFields(SObject objInstance, Type deserializeType, Map properties) {
+
+ Map mergedMap = new Map(objInstance.getPopulatedFieldsAsMap());
+ for (SObjectField field : properties.keySet()) {
+ // Merge the values from the properties map into the fields already set on the object
+ mergedMap.put(field.getDescribe().getName(), properties.get(field));
+ }
+ // Serialize the merged map, and then deserialize it as the desired object type.
+ String jsonString = JSON.serializePretty(mergedMap);
+ return (SObject) JSON.deserialize(jsonString, deserializeType);
+ }
+
+ /**
+ * Helper Methods
+ */
+ private static Object deserializeParentsAndChildren(
+ Type parentsType,
+ DescribeSObjectResult parentDescribe,
+ SObjectField relationshipField,
+ List parents,
+ List> children
+ ) {
+ List childRelationships = parentDescribe.getChildRelationships();
+
+ String relationshipName = null;
+ for(Schema.ChildRelationship childRelationship : childRelationships) {
+ if(childRelationship.getField() == relationshipField) {
+ relationshipName = childRelationship.getRelationshipName();
+ break;
+ }
+ }
+
+ // Stream the parsed JSON representation of the parent objects back out, injecting children as it goes
+ JSONParser parentsParser = JSON.createParser(JSON.serialize(parents));
+ JSONParser childrenParser = JSON.createParser(JSON.serialize(children));
+ JSONGenerator combinedOutput = JSON.createGenerator(false);
+ streamTokens(parentsParser, combinedOutput, new InjectChildrenEventHandler(childrenParser, relationshipName, children) );
+
+ // Derserialise back into SObject list complete with children
+ return JSON.deserialize(combinedOutput.getAsString(), parentsType);
+ }
+
+ /**
+ * Monitors stream events for end of object for each SObject contained in the parent list
+ * then injects the respective childs record list into the stream
+ */
+ private class InjectChildrenEventHandler implements JSONParserEvents
+ {
+ private JSONParser childrenParser;
+ private String relationshipName;
+ private List> children;
+ private Integer childListIdx = 0;
+
+ public InjectChildrenEventHandler(JSONParser childrenParser, String relationshipName, List> children) {
+ this.childrenParser = childrenParser;
+ this.relationshipName = relationshipName;
+ this.children = children;
+ this.childrenParser.nextToken(); // Consume the outer array token
+ }
+
+ public void nextToken(JSONParser fromStream, Integer depth, JSONGenerator toStream) {
+ // Inject children?
+ JSONToken currentToken = fromStream.getCurrentToken();
+ if(depth == 2 && currentToken == JSONToken.END_OBJECT ) {
+ toStream.writeFieldName(relationshipName);
+ toStream.writeStartObject();
+ toStream.writeNumberField('totalSize', children[childListIdx].size());
+ toStream.writeBooleanField('done', true);
+ toStream.writeFieldName('records');
+ streamTokens(childrenParser, toStream, null);
+ toStream.writeEndObject();
+ childListIdx++;
+ }
+ }
+ }
+
+ /**
+ * Utility function to stream tokens from a reader to a write, while providing a basic eventing model
+ */
+ private static void streamTokens(JSONParser fromStream, JSONGenerator toStream, JSONParserEvents events)
+ {
+ Integer depth = 0;
+ while (fromStream.nextToken()!=null)
+ {
+ // Give event handler chance to inject
+ if(events!=null)
+ events.nextToken(fromStream, depth, toStream);
+ // Forward to output stream
+ JSONToken currentToken = fromStream.getCurrentToken();
+ if(currentToken == JSONToken.START_ARRAY) {
+ toStream.writeStartArray();
+ depth++;
+ }
+ else if(currentToken == JSONToken.START_OBJECT) {
+ toStream.writeStartObject();
+ depth++;
+ }
+ else if(currentToken == JSONToken.FIELD_NAME)
+ toStream.writeFieldName(fromStream.getCurrentName());
+ else if(currentToken == JSONToken.VALUE_STRING ||
+ currentToken == JSONToken.VALUE_FALSE ||
+ currentToken == JSONToken.VALUE_TRUE ||
+ currentToken == JSONToken.VALUE_NUMBER_FLOAT ||
+ currentToken == JSONToken.VALUE_NUMBER_INT)
+ toStream.writeString(fromStream.getText());
+ else if(currentToken == JSONToken.END_OBJECT) {
+ toStream.writeEndObject();
+ depth--;
+ }
+ else if(currentToken == JSONToken.END_ARRAY) {
+ toStream.writeEndArray();
+ depth--;
+ }
+ // Don't continue to stream beyond the initial starting point
+ if(depth==0)
+ break;
+ }
+ }
+
+ /**
+ * Basic event used during the above streaming
+ */
+ private interface JSONParserEvents
+ {
+ void nextToken(JSONParser fromStream, Integer depth, JSONGenerator toStream);
+ }
+
+ /**
+ * Gets the SObjectType by name
+ */
+ private static Schema.SObjectType getType(String typeName) {
+ Map gd = Schema.getGlobalDescribe();
+ SObjectType sobjType = gd.get(typeName);
+ if (sobjType == null) {
+ throw new fflib_ApexMocks.ApexMocksException('SObject type not found: ' + typeName);
+ }
+ return sobjType;
+ }
+
+ /**
+ * Gets the SObjectField of an object by name
+ */
+ private static Schema.SObjectField getField(String objectName, String fieldName) {
+
+ SObjectType sobjType = getType(objectName);
+ Map objectFields = sobjType.getDescribe().fields.getMap();
+ Schema.SObjectField sobjField = objectFields.get(fieldName);
+ if (sobjField == null) {
+ throw new fflib_ApexMocks.ApexMocksException('SObject field not found: ' + fieldName);
+ }
+ return sobjField;
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_ApexMocksUtils.cls-meta.xml b/sfdx-source/apex-mocks/fflib_ApexMocksUtils.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_ApexMocksUtils.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_ArgumentCaptor.cls b/sfdx-source/apex-mocks/fflib_ArgumentCaptor.cls
new file mode 100644
index 00000000000..cfd5bd304fd
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_ArgumentCaptor.cls
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2016, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * This class implements the capturing framework for ApexMocks
+ * According to Mockito's syntax the type is passed in the capturer construction,
+ * however Apex cannot perform the auto casting that Java can.
+ * To be consistent with Mockito, the capturer does not perform any checks on the type of the argument.
+ * @group Core
+ */
+public with sharing class fflib_ArgumentCaptor
+{
+ protected List argumentsCaptured = new List();
+
+ /**
+ * Factory method to create a new fflib_ArgumentCaptor.
+ * Takes the captured argument's Type for consistency with Mockito syntax.
+ * The Type is IGNORED because we can't determine an object instance's Type at runtime unlike in Java.
+ * Rigorous type checking may be introduced in a future release, so you should specify the expected argument type correctly.
+ *
+ * @param ignoredCaptureType Type (class) of the captured argument
+ * @return A new fflib_ArgumentCaptor.
+ */
+ public static fflib_ArgumentCaptor forClass(Type ignoredCaptureType)
+ {
+ return new fflib_ArgumentCaptor();
+ }
+
+ /**
+ * Use it to capture the argument. This method must be used inside verification.
+ * Internally, this method registers a special implementation of a Matcher.
+ * This argument matcher stores the argument value so that you can use it later to perform assertions.
+ *
+ * @return a special matcher that matches any argument and remembers the value.
+ */
+ public Object capture()
+ {
+ AnyObject myMatcher = new AnyObject(this);
+
+ return fflib_Match.matches(myMatcher);
+ }
+
+ /**
+ * Returns the captured value of the argument. When capturing all arguments use getAllValues().
+ * If verified method was called multiple times then this method returns the latest captured value.
+ *
+ * @return captured argument value.
+ */
+ public Object getValue()
+ {
+ if( argumentsCaptured == null ||
+ argumentsCaptured.size() == 0)
+ {
+ return null;
+ }
+
+ //returns the last argument called
+ return argumentsCaptured.get( argumentsCaptured.size() - 1 );
+ }
+
+ /**
+ * Returns all captured values. Use it when capturing multiple arguments or when the verified method was called multiple times.
+ * When capturing multiple arguments is called multiple times, this method returns a merged list of all values from all invocations.
+ *
+ * @return Returns all captured values. Use it when capturing multiple arguments on the same call or when the verified method was called multiple times.
+ */
+ public List getAllValues()
+ {
+ return argumentsCaptured;
+ }
+
+ public class AnyObject implements fflib_IMatcher
+ {
+ private fflib_ArgumentCaptor captor;
+ private Object value;
+
+ public AnyObject(fflib_ArgumentCaptor captor)
+ {
+ this.captor = captor;
+ }
+
+ //match with all the possible values and store the arg value
+ public Boolean matches(Object arg)
+ {
+ value = arg;
+ return true;
+ }
+
+ //store the argument in the list ( this would be called inside the method counter where is compared with the matchers of the method)
+ public void storeArgument()
+ {
+ captor.argumentsCaptured.add(value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_ArgumentCaptor.cls-meta.xml b/sfdx-source/apex-mocks/fflib_ArgumentCaptor.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_ArgumentCaptor.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_IDGenerator.cls b/sfdx-source/apex-mocks/fflib_IDGenerator.cls
new file mode 100644
index 00000000000..ddeb27c9dfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_IDGenerator.cls
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2014, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public with sharing class fflib_IDGenerator
+{
+ private static Integer fakeIdCount = 0;
+ private static final String ID_PATTERN = '000000000000';
+
+ /**
+ * Generate a fake Salesforce Id for the given SObjectType
+ */
+ public static Id generate(Schema.SObjectType sobjectType)
+ {
+ String keyPrefix = sobjectType.getDescribe().getKeyPrefix();
+ fakeIdCount++;
+
+ String fakeIdPrefix = ID_PATTERN.substring(0, ID_PATTERN.length() - String.valueOf(fakeIdCount).length());
+
+ return Id.valueOf(keyPrefix + fakeIdPrefix + fakeIdCount);
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_IDGenerator.cls-meta.xml b/sfdx-source/apex-mocks/fflib_IDGenerator.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_IDGenerator.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_IMatcher.cls b/sfdx-source/apex-mocks/fflib_IMatcher.cls
new file mode 100644
index 00000000000..d8b9729a36d
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_IMatcher.cls
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2014-2016, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public interface fflib_IMatcher
+{
+ /**
+ * Whether or not the supplied argument is a match.
+ * Any supplementary information (e.g. boundary conditions, objects to match to etc)
+ * should be cached by the matcher constructor.
+ * @param arg The argument value supplied to the method
+ * @return Boolean True if the argument value is a match, false otherwise.
+ */
+ Boolean matches(Object arg);
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_IMatcher.cls-meta.xml b/sfdx-source/apex-mocks/fflib_IMatcher.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_IMatcher.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_InOrder.cls b/sfdx-source/apex-mocks/fflib_InOrder.cls
new file mode 100644
index 00000000000..39d0bf59ad2
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_InOrder.cls
@@ -0,0 +1,356 @@
+/*
+ Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+
+/**
+ * @group Core
+ */
+public with sharing class fflib_InOrder extends fflib_MethodVerifier
+{
+ private final List unorderedMockInstances;
+ private Integer idxMethodCall = 0;
+
+ private Set notImplementedMethods =
+ new Set
+ {
+ fflib_VerificationMode.ModeName.atMost,
+ fflib_VerificationMode.ModeName.between
+ };
+
+ private final fflib_ApexMocks mocks;
+
+ /**
+ * Construct the InOrder instance.
+ * @param mocks The apex mock object instance.
+ * @param unorderedMockInstances One or more mock implementation classes (listed in any order), whose ordered method calls require verification.
+ */
+ public fflib_InOrder(fflib_ApexMocks mocks, List unorderedMockInstances)
+ {
+ this.unorderedMockInstances = unorderedMockInstances;
+ this.mocks = mocks;
+ }
+
+ /**
+ * Verify a method was called on a mock object.
+ * It performs a no strict ordered verification.
+ * The verification could be either greedy or not depending of the verificationMode passed.
+ * Check the fflib_VerificationMode methods for details.
+ * @param mockInstance The mock object instance.
+ * @param verificationMode Defines the constraints for performing the verification (e.g. the minimum and maximum expected invocation counts).
+ * @return The mock object instance.
+ */
+ public Object verify(Object mockInstance, fflib_VerificationMode verificationMode)
+ {
+ mocks.setOrderedVerifier(this);
+ return mocks.verify(mockInstance, verificationMode);
+ }
+
+ /**
+ * Verify a method was called on a mock object.
+ * It performs the default times(1) verification for the InOrder.
+ * @param mockInstance The mock object instance.
+ * @return The mock object instance.
+ */
+ public Object verify(Object mockInstance)
+ {
+ mocks.setOrderedVerifier(this);
+ return mocks.verify(mockInstance);
+ }
+
+ /**
+ * Verify a method was called on a mock object.
+ * Wrapper for the new syntax call to be conformed to the old style notation
+ * It performs the equivalent of times(times) verification for the InOrder.
+ * @param mockInstance The mock object instance.
+ * @param times The number of times you expect the method to have been called.
+ * @return The mock object instance.
+ */
+ public Object verify(Object mockInstance, Integer times)
+ {
+ mocks.setOrderedVerifier(this);
+ return mocks.verify(mockInstance, times);
+ }
+
+ /**
+ * Verify that after the last successful verified method no more interactions happened on the inOrderMock instance.
+ * @throws Exception with message to help to identify the last method called.
+ */
+ public void verifyNoMoreInteractions()
+ {
+ if(idxMethodCall == 0)
+ {
+ verifyNoInteractions();
+ }
+
+ if(hasNextInteraction(unorderedMockInstances, idxMethodCall))
+ {
+ fflib_InvocationOnMock invocation =
+ fflib_MethodCountRecorder.getOrderedMethodCalls().get(idxMethodCall -1);
+
+ throw new fflib_ApexMocks.ApexMocksException(
+ 'No more Interactions were expected after the ' + invocation.getMethod() +' method.');
+ }
+ }
+
+ /**
+ * Verify that no interactions at all happened on the inOrderMock instance.
+ * @throws Exception with message.
+ */
+ public void verifyNoInteractions()
+ {
+ if(hasNextInteraction(unorderedMockInstances, 0))
+ {
+ throw new fflib_ApexMocks.ApexMocksException(
+ 'No Interactions expected on this InOrder Mock instance!');
+ }
+ }
+
+ /*
+ * Verifies a method was invoked the expected number of times, with the expected arguments.
+ * The in-order verifier remembers the last method invocation it successfully verified,
+ * and only considers subsequent method invocations for subsequent verifications.
+ * @param qualifiedMethod The method to be verified.
+ * @param expectedArguments The arguments of the method that needs to be verified.
+ * @param verificationMode The verification mode that holds the setting about how the verification should be performed.
+ */
+ protected override void verify(
+ fflib_QualifiedMethod qm,
+ fflib_MethodArgValues expectedArguments,
+ fflib_VerificationMode verificationMode)
+ {
+ String inOrder = ' in order';
+ List matchers = fflib_Match.Matching ? fflib_Match.getAndClearMatchers(expectedArguments.argValues.size()) : null;
+ List actualInvocations = fflib_MethodCountRecorder.getOrderedMethodCalls();
+ List actualArguments = new List();
+ for (fflib_InvocationOnMock invocation : actualInvocations)
+ {
+ actualArguments.add(invocation.getMethodArgValues());
+ }
+
+ if( verificationMode.VerifyMin == 0 && verificationMode.VerifyMax == 0)
+ {
+ Integer methodCounts = countInteractions(matchers, qm, expectedArguments);
+ if(methodCounts != 0 )
+ throwException(qm, inOrder, fflib_ApexMocks.NEVER, '', methodCounts, verificationMode.CustomAssertMessage, expectedArguments, matchers, actualArguments);
+ }
+
+ Integer i=0;
+ for ( ; i matchers,
+ fflib_QualifiedMethod qm,
+ fflib_MethodArgValues methodArg)
+ {
+ fflib_InvocationOnMock calledMethod = getNextMethodCall();
+ while(calledMethod != null)
+ {
+ if(calledMethod.getMethod() == qm &&
+ argumentsMatch(calledMethod.getMethodArgValues(), matchers, methodArg))
+ {
+ //it's our method
+ if (matchers != null)
+ {
+ capture(matchers);
+ }
+ return true;
+ }
+
+ calledMethod = getNextMethodCall();
+ }
+
+ return false;
+ }
+
+ private Integer countInteractions(
+ List matchers,
+ fflib_QualifiedMethod qualifiedMethod,
+ fflib_MethodArgValues methodArg)
+ {
+ Integer interactionsCouter = 0;
+
+ for (Integer i = idxMethodCall, len = fflib_MethodCountRecorder.getOrderedMethodCalls().size(); i matchers,
+ fflib_QualifiedMethod qualifiedMethod,
+ fflib_MethodArgValues methodArg)
+ {
+ Integer lastInteracionIndex = 0;
+
+ //going all through the orderedMethodCalls to find all the interaction of the method
+ for (Integer i = idxMethodCall, len = fflib_MethodCountRecorder.getOrderedMethodCalls().size(); i matchers,
+ fflib_MethodArgValues methodArg)
+ {
+ //Check it was called with the right args.
+ if (matchers != null)
+ {
+ if(fflib_Match.matchesAllArgs(calledMethodArg, matchers))
+ {
+ //Return now we've matched the method call
+ return true;
+ }
+ }
+ else if(calledMethodArg == methodArg)
+ {
+ //Return now we've matched the method call
+ return true;
+ }
+
+ return false;
+ }
+
+ private fflib_InvocationOnMock getNextMethodCall()
+ {
+ return getNextMethodCall(true);
+ }
+
+ private fflib_InvocationOnMock getNextMethodCall(Boolean updateIdxMethodCall)
+ {
+ Integer idx = 0;
+ for (fflib_InvocationOnMock invocation : fflib_MethodCountRecorder.getOrderedMethodCalls())
+ {
+ if (idx == idxMethodCall)
+ {
+ if(isForMockInstance(invocation))
+ {
+ if(updateIdxMethodCall)
+ idxMethodCall++;
+ return invocation;
+ }
+ }
+ else
+ {
+ idx++;
+ }
+ }
+
+ return null;
+ }
+
+ private Boolean isForMockInstance(fflib_InvocationOnMock invocation)
+ {
+ for (Object mi : unorderedMockInstances)
+ {
+ if (mi === invocation.getMock())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ * Used by the fflib_InOrder invocation verifier to find further interactions with a given mock instances.
+ * @param mockInstances The tracked mock instances - only methods called on these objects are counted as an invocation.
+ * @param idxLastMethodCalled The index of the last matched method, used to offset the search for invocations so we don't double count invocations.
+ * @return Whether or not there were further interactions.
+ */
+ private Boolean hasNextInteraction(List mockInstances, Integer idxLastMethodCalled)
+ {
+ Integer idx = 0;
+
+ for (fflib_InvocationOnMock methodCall : fflib_MethodCountRecorder.getOrderedMethodCalls())
+ {
+ if (isForMockInstance(methodCall))
+ {
+ idx++;
+ if (idx > idxLastMethodCalled)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ * Method that validate the verification mode used in the verify.
+ * Not all the methods from the fflib_VerificationMode are implemented for the different classes that extends the fflib_MethodVerifier.
+ * The error is thrown at run time, so this method is called in the method that actually performs the verify.
+ * @param verificationMode The verification mode that have to been verified.
+ * @throws Exception with message for the fflib_VerificationMode not implemented.
+ */
+ protected override void validateMode(fflib_VerificationMode verificationMode)
+ {
+ if(notImplementedMethods.contains(verificationMode.Method))
+ {
+ throw new fflib_ApexMocks.ApexMocksException(
+ 'The ' + verificationMode.Method.name() + ' method is not implemented for the fflib_InOrder class');
+ }
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_InOrder.cls-meta.xml b/sfdx-source/apex-mocks/fflib_InOrder.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_InOrder.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_Inheritor.cls b/sfdx-source/apex-mocks/fflib_Inheritor.cls
new file mode 100644
index 00000000000..d718ecf67fa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_Inheritor.cls
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2016-2017 FinancialForce.com, inc. All rights reserved.
+ */
+@isTest
+public class fflib_Inheritor implements IA, IB, IC
+{
+ public interface IA {String doA();}
+ public interface IB {String doB();}
+ public interface IC {String doC();}
+
+ public String doA(){return 'Did A';}
+ public String doB(){return 'Did B';}
+ public String doC(){return 'Did C';}
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_Inheritor.cls-meta.xml b/sfdx-source/apex-mocks/fflib_Inheritor.cls-meta.xml
new file mode 100644
index 00000000000..f539c3a316c
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_Inheritor.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_InvocationOnMock.cls b/sfdx-source/apex-mocks/fflib_InvocationOnMock.cls
new file mode 100644
index 00000000000..9e13914fafc
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_InvocationOnMock.cls
@@ -0,0 +1,84 @@
+/*
+ Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * An invocation on a mock.
+ * A place holder for mock, the method that was called and the arguments that were passed.
+ * @group Core
+ */
+public with sharing class fflib_InvocationOnMock
+{
+ private fflib_QualifiedMethod qm;
+ private fflib_MethodArgValues methodArg;
+ private Object mockInstance;
+
+ /**
+ * Constructor for the class.
+ * @param qm The fflib_QualifiedMethod instance to be stored.
+ * @param args The fflib_MethodArgValues instance to be stored.
+ * @param mockInstance The mock instance to be stored.
+ */
+ public fflib_InvocationOnMock(fflib_QualifiedMethod qm, fflib_MethodArgValues args, Object mockInstance)
+ {
+ this.qm = qm;
+ this.methodArg = args;
+ this.mockInstance = mockInstance;
+ }
+
+ /**
+ * Returns the argument at the given index.
+ * @param index The index of the wanted argument.
+ * @throws ApexMocksException in case the index is out of range.
+ * @return The argument at the given index.
+ */
+ public Object getArgument(Integer index)
+ {
+ validateIndex(index);
+ return methodArg.argValues[index];
+ }
+
+ /**
+ * Returns the list of arguments passed to the method.
+ * @return The list of arguments.
+ */
+ public List getArguments()
+ {
+ return methodArg.argValues;
+ }
+
+ /**
+ * Returns fflib_MethodArgValues instance that represents the arguments passed to the method.
+ * @return The fflib_MethodArgValues instance that represents the arguments passed to the method.
+ */
+ public fflib_MethodArgValues getMethodArgValues()
+ {
+ return methodArg;
+ }
+
+ /**
+ * Returns the fflib_QualifiedMethod instance that represent the fully qualified method called within the invocation.
+ * @return The method stored in the invocation.
+ */
+ public fflib_QualifiedMethod getMethod()
+ {
+ return qm;
+ }
+
+ /**
+ * Returns the mock object on which the invocation occurs.
+ * @return The mock object on which the invocation occurs.
+ */
+ public Object getMock()
+ {
+ return mockInstance;
+ }
+
+ private void validateIndex(Integer index)
+ {
+ if(index < 0 || index >= methodArg.argValues.size())
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Invalid index, must be greater or equal to zero and less of ' + methodArg.argValues.size()+'.');
+ }
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_InvocationOnMock.cls-meta.xml b/sfdx-source/apex-mocks/fflib_InvocationOnMock.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_InvocationOnMock.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_Match.cls b/sfdx-source/apex-mocks/fflib_Match.cls
new file mode 100644
index 00000000000..30a09202d3f
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_Match.cls
@@ -0,0 +1,1158 @@
+/**
+ * Copyright (c) 2014, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public class fflib_Match
+{
+ private static List matchers = new List();
+
+ /**
+ * Matching
+ * True when comparing method arg values to matchers, false when comparing absolute arg values.
+ */
+ public static Boolean Matching = false;
+
+ /**
+ * Used internally by the mocking framework, you shouldn't need to call this method directly.
+ * Copies the registered matchers, and then switches matching mode off.
+ * @param expectedSize The expected number of matchers to be returned. If this does not match the actual value an expection is thrown.
+ * @return List The registered matchers, collected while in matching mode.
+ */
+ public static List getAndClearMatchers(Integer expectedSize)
+ {
+ Matching = false;
+
+ List retval = matchers.clone();
+ matchers.clear();
+
+ if (retval.size() != expectedSize)
+ {
+ throw new fflib_ApexMocks.ApexMocksException('The number of matchers defined (' + retval.size() + ').'
+ + ' does not match the number expected (' + expectedSize + ')\n'
+ + 'If you are using matchers all arguments must be passed in as matchers.\n'
+ + 'For example myList.add(fflib_Match.anyInteger(), \'String\') should be defined as myList.add(fflib_Match.anyInteger(), fflib_Match.eq(\'String\')).');
+ }
+
+ return retval;
+ }
+
+ /**
+ * Used internally by the mocking framework, you shouldn't need to call this method directly.
+ * Compares all supplied method arg values to the supplied target matchers.
+ * @param methodArg The arguments supplied when the method was called
+ * @param targetMatchers The matchers the arguments need to be compared with
+ * @throws fflib_ApexMocks.ApexMocksException Thrown when methodArgValues is null/empty, targetMatchers is null, or their sizes don't match
+ * @return Boolean True if all arg values satisfy all of the supplied matchers.
+ */
+ public static Boolean matchesAllArgs(fflib_MethodArgValues methodArg, List targetMatchers)
+ {
+ validateArgs(methodArg, targetMatchers);
+
+ Integer matchersSize = targetMatchers.size();
+ for (Integer i=0; i targetMatchers)
+ {
+ if (methodArg == null)
+ {
+ throw new fflib_ApexMocks.ApexMocksException('MethodArgs cannot be null');
+ }
+
+ if (methodArg.argValues == null)
+ {
+ throw new fflib_ApexMocks.ApexMocksException('MethodArgs.argValues cannot be null');
+ }
+
+ if (targetMatchers == null)
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Matchers cannot be null');
+ }
+
+ if (targetMatchers.size() != methodArg.argValues.size())
+ {
+ throw new fflib_ApexMocks.ApexMocksException('MethodArgs and matchers must have the same count'
+ + ', MethodArgs: (' + methodArg.argValues.size() + ') ' + methodArg.argValues
+ + ', Matchers: (' + targetMatchers.size() + ') ' + targetMatchers);
+ }
+ }
+
+ /**
+ * Registers a matcher which will be stubbed/verified against.
+ * @param matcher The matcher that needs to be compared
+ * @return Object Always returns null. This can then be cast into the correct arg type
+ * so that the right method is called on the mock objects.
+ */
+ public static Object matches(fflib_IMatcher matcher)
+ {
+ Matching = true;
+ matchers.add(matcher);
+
+ return null;
+ }
+
+ /**
+ * COMBINED MATCHER
+ * The allOf, anyOf and noneOf methods are overloaded to provide fluent matcher calls for up to 4 matcher conditions.
+ * To connect 5 or more, the List version directly.
+ */
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches allOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)public static Object allOf(Object o1, Object o2)
+ */
+ public static Object allOf(Object o1, Object o2)
+ {
+ return allOf(new Object[]{ o1, o2 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches allOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @param o3 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object allOf(Object o1, Object o2, Object o3)
+ {
+ return allOf(new Object[]{ o1, o2, o3 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches allOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @param o3 A dummy object returned by registering another matcher
+ * @param o4 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object allOf(Object o1, Object o2, Object o3, Object o4)
+ {
+ return allOf(new Object[]{ o1, o2, o3, o4 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches allOf
+ * @param o A list of dummy objects returned by registering other matchers
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object allOf(List o)
+ {
+ return combined(fflib_MatcherDefinitions.Connective.ALL, o);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object anyOf(Object o1, Object o2)
+ {
+ return anyOf(new Object[]{ o1, o2 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @param o3 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object anyOf(Object o1, Object o2, Object o3)
+ {
+ return anyOf(new Object[]{ o1, o2, o3 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @param o3 A dummy object returned by registering another matcher
+ * @param o4 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object anyOf(Object o1, Object o2, Object o3, Object o4)
+ {
+ return anyOf(new Object[]{ o1, o2, o3, o4 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyOf
+ * @param o A list of dummy objects returned by registering other matchers
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object anyOf(List o)
+ {
+ return combined(fflib_MatcherDefinitions.Connective.AT_LEAST_ONE, o);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches isNot
+ * @param o1 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object isNot(Object o1)
+ {
+ return noneOf(new Object[]{ o1 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches noneOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object noneOf(Object o1, Object o2)
+ {
+ return noneOf(new Object[]{ o1, o2 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches noneOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @param o3 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object noneOf(Object o1, Object o2, Object o3)
+ {
+ return noneOf(new Object[]{ o1, o2, o3 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches noneOf
+ * @param o1 A dummy object returned by registering another matcher
+ * @param o2 A dummy object returned by registering another matcher
+ * @param o3 A dummy object returned by registering another matcher
+ * @param o4 A dummy object returned by registering another matcher
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object noneOf(Object o1, Object o2, Object o3, Object o4)
+ {
+ return noneOf(new Object[]{ o1, o2, o3, o4 });
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches noneOf
+ * @param o A list of dummy objects returned by registering other matchers
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object noneOf(List o)
+ {
+ return combined(fflib_MatcherDefinitions.Connective.NONE, o);
+ }
+
+ private static Object combined(fflib_MatcherDefinitions.Connective connectiveExpression, List o)
+ {
+ return matches(new fflib_MatcherDefinitions.Combined(connectiveExpression, (gatherMatchers(o))));
+ }
+
+ private static List gatherMatchers(Object[] ignoredMatcherObjects)
+ {
+ if (ignoredMatcherObjects == null || ignoredMatcherObjects.isEmpty())
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Must register matchers to combine');
+ }
+
+ //Each ignored matcher object represents a matcher that has been registered against fflib_Match.matchers,
+ //but is actually for the connective matchers.
+ List innerMatchers = new List();
+
+ Integer innerMatcherCount = ignoredMatcherObjects.size();
+ while (innerMatchers.size() < innerMatcherCount)
+ {
+ if (matchers.isEmpty())
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Error reclaiming inner matchers for combined matcher. Wanted '
+ + innerMatcherCount + ' matchers but only got ' + innerMatchers);
+ }
+
+ fflib_IMatcher innerMatcher = matchers.remove(matchers.size()-1);
+
+ //Add to the start of the list to preserve the order in which matchers were declared.
+ //Note. Apex throws list index out of bounds if inserting an element into an empty list at index 0S
+ if (!innerMatchers.isEmpty())
+ {
+ innerMatchers.add(0, innerMatcher);
+ }
+ else
+ {
+ innerMatchers.add(innerMatcher);
+ }
+ }
+
+ return innerMatchers;
+ }
+
+ /**
+ * ALL OTHER MATCHER METHODS
+ */
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eq
+ * @param toMatch The Object to be compared
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object eq(Object toMatch)
+ {
+ return matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqBoolean
+ * @param toMatch The Boolean to be compared
+ * @return Boolean A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Boolean eqBoolean(Boolean toMatch)
+ {
+ return (Boolean)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqDate
+ * @param toMatch The Date to be compared
+ * @return Date A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Date eqDate(Date toMatch)
+ {
+ return (Date)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqDatetime
+ * @param toMatch The Datetime to be compared
+ * @return Datetime A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Datetime eqDatetime(Datetime toMatch)
+ {
+ return (Datetime)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqDecimal
+ * @param toMatch The Decimal to be compared
+ * @return Decimal A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Decimal eqDecimal(Decimal toMatch)
+ {
+ return (Decimal)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqDouble
+ * @param toMatch The Double to be compared
+ * @return Double A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Double eqDouble(Double toMatch)
+ {
+ return (Double)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqId
+ * @param toMatch The Id to be compared
+ * @return Id A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Id eqId(Id toMatch)
+ {
+ return (Id)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqInteger
+ * @param toMatch The Integer to be compared
+ * @return Integer A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Integer eqInteger(Integer toMatch)
+ {
+ return (Integer)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqList
+ * @param toMatch The List to be compared
+ * @return List A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static List eqList(List toMatch)
+ {
+ return (List)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqLong
+ * @param toMatch The Long to be compared
+ * @return Long A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Long eqLong(Long toMatch)
+ {
+ return (Long)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqSObjectField
+ * @param toMatch The SObjectField to be compared
+ * @return SObjectField A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static SObjectField eqSObjectField(SObjectField toMatch)
+ {
+ return (SObjectField)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqSObjectType
+ * @param toMatch The SObjectType to be compared
+ * @return SObjectType A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static SObjectType eqSObjectType(SObjectType toMatch)
+ {
+ return (SObjectType)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches eqString
+ * @param toMatch The String to be compared
+ * @return String A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static String eqString(String toMatch)
+ {
+ return (String)matches(new fflib_MatcherDefinitions.Eq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches refEq
+ * @param toMatch The Object to be compared
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object refEq(Object toMatch)
+ {
+ return matches(new fflib_MatcherDefinitions.RefEq(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyBoolean
+ * @return Boolean A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Boolean anyBoolean()
+ {
+ return (Boolean)matches(new fflib_MatcherDefinitions.AnyBoolean());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyDate
+ * @return Date A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Date anyDate()
+ {
+ return (Date)matches(new fflib_MatcherDefinitions.AnyDate());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyDatetime
+ * @return Datetime A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Datetime anyDatetime()
+ {
+ return (Datetime)matches(new fflib_MatcherDefinitions.AnyDatetime());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyDecimal
+ * @return Decimal A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Decimal anyDecimal()
+ {
+ return (Decimal)matches(new fflib_MatcherDefinitions.AnyDecimal());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyDouble
+ * @return Double A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Double anyDouble()
+ {
+ return (Double)matches(new fflib_MatcherDefinitions.AnyDouble());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyFieldSet
+ * @return Schema.FieldSet A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Schema.FieldSet anyFieldSet()
+ {
+ return (Schema.FieldSet)matches(new fflib_MatcherDefinitions.AnyFieldSet());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyId
+ * @return Id A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Id anyId()
+ {
+ return (Id)matches(new fflib_MatcherDefinitions.AnyId());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyInteger
+ * @return Integer A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Integer anyInteger()
+ {
+ return (Integer)matches(new fflib_MatcherDefinitions.AnyInteger());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyList
+ * @return List A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static List anyList()
+ {
+ return (List)matches(new fflib_MatcherDefinitions.AnyList());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyLong
+ * @return Long A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Long anyLong()
+ {
+ return (Long)matches(new fflib_MatcherDefinitions.AnyLong());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyObject
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object anyObject()
+ {
+ return matches(new fflib_MatcherDefinitions.AnyObject());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anyString
+ * @return String A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static String anyString()
+ {
+ return (String)matches(new fflib_MatcherDefinitions.AnyString());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anySObject
+ * @return SObject A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static SObject anySObject()
+ {
+ return (SObject)matches(new fflib_MatcherDefinitions.AnySObject());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anySObjectField
+ * @return SObjectField A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static SObjectField anySObjectField()
+ {
+ return (SObjectField)matches(new fflib_MatcherDefinitions.AnySObjectField());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches anySObjectType
+ * @return SObjectType A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static SObjectType anySObjectType()
+ {
+ return (SObjectType)matches(new fflib_MatcherDefinitions.AnySObjectType());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches dateAfter (not inclusive)
+ * @param fromDate The Date to be compared
+ * @return Date A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Date dateAfter(Date fromDate)
+ {
+ return dateAfter(fromDate, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches dateAfter
+ * @param fromDate The Date to be compared
+ * @param inclusive Whether or not a Date equal to fromDate should be considered a match
+ * @return Date A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Date dateAfter(Date fromDate, Boolean inclusive)
+ {
+ return (Date)matches(new fflib_MatcherDefinitions.DatetimeAfter(fromDate, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches dateBefore (not inclusive)
+ * @param toDate The Date to be compared
+ * @return Date A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Date dateBefore(Date toDate)
+ {
+ return dateBefore(toDate, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches dateBefore
+ * @param toDate The Date to be compared
+ * @param inclusive Whether or not a Date equal to toDate should be considered a match
+ * @return Date A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Date dateBefore(Date toDate, Boolean inclusive)
+ {
+ return (Date)matches(new fflib_MatcherDefinitions.DatetimeBefore(toDate, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches dateBetween (not inclusive)
+ * @param fromDate The lower bound Date to be compared
+ * @param toDate The upper bound Date to be compared
+ * @return Date A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Date dateBetween(Date fromDate, Date toDate)
+ {
+ return dateBetween(fromDate, false, toDate, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches dateBetween
+ * @param fromDate The lower bound Date to be compared
+ * @param inclusiveFrom Whether or not a Date equal to fromDate should be considered a match
+ * @param toDate The upper bound Date to be compared
+ * @param inclusiveTo Whether or not a Date equal to toDate should be considered a match
+ * @return Date A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Date dateBetween(Date fromDate, Boolean inclusiveFrom, Date toDate, Boolean inclusiveTo)
+ {
+ return (Date)matches(new fflib_MatcherDefinitions.DatetimeBetween(fromDate, inclusiveFrom, toDate, inclusiveTo));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches datetimeAfter (not inclusive)
+ * @param fromDate The Datetime to be compared
+ * @return Datetime A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Datetime datetimeAfter(Datetime fromDate)
+ {
+ return datetimeAfter(fromDate, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches datetimeAfter
+ * @param fromDate The Datetime to be compared
+ * @param inclusive Whether or not a Datetime equal to fromDate should be considered a match
+ * @return Datetime A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Datetime datetimeAfter(Datetime fromDate, Boolean inclusive)
+ {
+ return (Datetime)matches(new fflib_MatcherDefinitions.DatetimeAfter(fromDate, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches datetimeBefore (not inclusive)
+ * @param toDate The Datetime to be compared
+ * @return Datetime A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Datetime datetimeBefore(Datetime toDate)
+ {
+ return datetimeBefore(toDate, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches datetimeBefore
+ * @param toDate The Datetime to be compared
+ * @param inclusive Whether or not a Datetime equal to toDate should be considered a match
+ * @return Datetime A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Datetime datetimeBefore(Datetime toDate, Boolean inclusive)
+ {
+ return (Datetime)matches(new fflib_MatcherDefinitions.DatetimeBefore(toDate, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches datetimeBetween (not inclusive)
+ * @param fromDate The lower bound Datetime to be compared
+ * @param toDate The upper bound Datetime to be compared
+ * @return Datetime A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Datetime datetimeBetween(Datetime fromDate, Datetime toDate)
+ {
+ return datetimeBetween(fromDate, false, toDate, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches datetimeBetween
+ * @param fromDate The lower bound Datetime to be compared
+ * @param inclusiveFrom Whether or not a Datetime equal to fromDate should be considered a match
+ * @param toDate The upper bound Datetime to be compared
+ * @param inclusiveTo Whether or not a Datetime equal to toDate should be considered a match
+ * @return Datetime A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Datetime datetimeBetween(Datetime fromDate, Boolean inclusiveFrom, Datetime toDate, Boolean inclusiveTo)
+ {
+ return (Datetime)matches(new fflib_MatcherDefinitions.DatetimeBetween(fromDate, inclusiveFrom, toDate, inclusiveTo));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches decimalBetween (not inclusive)
+ * @param lower The lower number to be compared
+ * @param upper The upper number to be compared
+ * @return Decimal A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Decimal decimalBetween(Decimal lower, Decimal upper)
+ {
+ return decimalBetween(lower, false, upper, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches decimalBetween
+ * @param lower The lower number to be compared
+ * @param inclusiveLower Whether or not a number equal to the lower bound should be considered a match
+ * @param upper The upper number to be compared
+ * @param inclusiveUpper Whether or not a number equal to the upper bound should be considered a match
+ * @return Decimal A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Decimal decimalBetween(Decimal lower, Boolean inclusiveLower, Decimal upper, Boolean inclusiveUpper)
+ {
+ return (Decimal)matches(new fflib_MatcherDefinitions.DecimalBetween(lower, inclusiveLower, upper, inclusiveUpper));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches decimalLessThan (not inclusive)
+ * @param toMatch The number to be compared
+ * @return Decimal A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Decimal decimalLessThan(Decimal toMatch)
+ {
+ return decimalLessThan(toMatch, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches decimalLessThan
+ * @param toMatch The number to be compared
+ * @param inclusive Whether or not a number equal to toMatch should be considered a match
+ * @return Decimal A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Decimal decimalLessThan(Decimal toMatch, Boolean inclusive)
+ {
+ return (Decimal)matches(new fflib_MatcherDefinitions.DecimalLessThan(toMatch, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches decimalMoreThan (not inclusive)
+ * @param toMatch The number to be compared
+ * @return Decimal A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Decimal decimalMoreThan(Decimal toMatch)
+ {
+ return decimalMoreThan(toMatch, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches decimalMoreThan
+ * @param toMatch The number to be compared
+ * @param inclusive Whether or not a number equal to toMatch should be considered a match
+ * @return Decimal A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Decimal decimalMoreThan(Decimal toMatch, Boolean inclusive)
+ {
+ return (Decimal)matches(new fflib_MatcherDefinitions.DecimalMoreThan(toMatch, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches doubleBetween (not inclusive)
+ * @param lower The lower number to be compared
+ * @param upper The upper number to be compared
+ * @return Double A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Double doubleBetween(Double lower, Double upper)
+ {
+ return doubleBetween(lower, false, upper, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches doubleBetween
+ * @param lower The lower number to be compared
+ * @param inclusiveLower Whether or not a number equal to the lower bound should be considered a match
+ * @param upper The upper number to be compared
+ * @param inclusiveUpper Whether or not a number equal to the upper bound should be considered a match
+ * @return Double A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Double doubleBetween(Double lower, Boolean inclusiveLower, Double upper, Boolean inclusiveUpper)
+ {
+ return (Double)matches(new fflib_MatcherDefinitions.DecimalBetween(lower, inclusiveLower, upper, inclusiveUpper));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches doubleLessThan (not inclusive)
+ * @param toMatch The number to be compared
+ * @return Double A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Double doubleLessThan(Double toMatch)
+ {
+ return doubleLessThan(toMatch, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches doubleLessThan
+ * @param toMatch The number to be compared
+ * @param inclusive Whether or not a number equal to toMatch should be considered a match
+ * @return Double A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Double doubleLessThan(Double toMatch, Boolean inclusive)
+ {
+ return (Double)matches(new fflib_MatcherDefinitions.DecimalLessThan(toMatch, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches doubleMoreThan (not inclusive)
+ * @param toMatch The number to be compared
+ * @return Double A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Double doubleMoreThan(Double toMatch)
+ {
+ return doubleMoreThan(toMatch, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches doubleMoreThan
+ * @param toMatch The number to be compared
+ * @param inclusive Whether or not a number equal to toMatch should be considered a match
+ * @return Double A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Double doubleMoreThan(Double toMatch, Boolean inclusive)
+ {
+ return (Double)matches(new fflib_MatcherDefinitions.DecimalMoreThan(toMatch, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches FieldSetEquivalentTo
+ * @param toMatch The fieldSet to be compared
+ * @return Schema.FieldSet A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Schema.FieldSet fieldSetEquivalentTo(Schema.FieldSet toMatch)
+ {
+ return (Schema.FieldSet)matches(new fflib_MatcherDefinitions.FieldSetEquivalentTo(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches integerBetween (not inclusive)
+ * @param lower The lower number to be compared
+ * @param upper The upper number to be compared
+ * @return Integer A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Integer integerBetween(Integer lower, Integer upper)
+ {
+ return integerBetween(lower, false, upper, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches integerBetween
+ * @param lower The lower number to be compared
+ * @param inclusiveLower Whether or not a number equal to the lower bound should be considered a match
+ * @param upper The upper number to be compared
+ * @param inclusiveUpper Whether or not a number equal to the upper bound should be considered a match
+ * @return Integer A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Integer integerBetween(Integer lower, Boolean inclusiveLower, Integer upper, Boolean inclusiveUpper)
+ {
+ return (Integer)matches(new fflib_MatcherDefinitions.DecimalBetween(lower, inclusiveLower, upper, inclusiveUpper));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches integerLessThan (not inclusive)
+ * @param toMatch The number to be compared
+ * @return Integer A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Integer integerLessThan(Integer toMatch)
+ {
+ return integerLessThan(toMatch, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches integerLessThan
+ * @param toMatch The number to be compared
+ * @param inclusive Whether or not a number equal to toMatch should be considered a match
+ * @return Integer A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Integer integerLessThan(Integer toMatch, Boolean inclusive)
+ {
+ return (Integer)matches(new fflib_MatcherDefinitions.DecimalLessThan(toMatch, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches integerMoreThan (not inclusive)
+ * @param toMatch The number to be compared
+ * @return Integer A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Integer integerMoreThan(Integer toMatch)
+ {
+ return integerMoreThan(toMatch, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches integerMoreThan
+ * @param toMatch The number to be compared
+ * @param inclusive Whether or not a number equal to toMatch should be considered a match
+ * @return Integer A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Integer integerMoreThan(Integer toMatch, Boolean inclusive)
+ {
+ return (Integer)matches(new fflib_MatcherDefinitions.DecimalMoreThan(toMatch, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches isNotNull
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object isNotNull()
+ {
+ return matches(new fflib_MatcherDefinitions.IsNotNull());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches isNull
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object isNull()
+ {
+ return matches(new fflib_MatcherDefinitions.IsNull());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches listContains
+ * @param toMatch The list to be compared
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object listContains(Object toMatch)
+ {
+ return matches(new fflib_MatcherDefinitions.ListContains(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches listIsEmpty
+ * @return Object A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked. (You may need to cast down to your specific object type)
+ */
+ public static Object listIsEmpty()
+ {
+ return matches(new fflib_MatcherDefinitions.ListIsEmpty());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches longBetween (not inclusive)
+ * @param lower The lower number to be compared
+ * @param upper The upper number to be compared
+ * @return Long A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Long longBetween(Long lower, Long upper)
+ {
+ return longBetween(lower, false, upper, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches longBetween
+ * @param lower The lower number to be compared
+ * @param inclusiveLower Whether or not a number equal to the lower bound should be considered a match
+ * @param upper The upper number to be compared
+ * @param inclusiveUpper Whether or not a number equal to the upper bound should be considered a match
+ * @return Long A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Long longBetween(Long lower, Boolean inclusiveLower, Long upper, Boolean inclusiveUpper)
+ {
+ return (Long)matches(new fflib_MatcherDefinitions.DecimalBetween(lower, inclusiveLower, upper, inclusiveUpper));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches longLessThan (not inclusive)
+ * @param toMatch The number to be compared
+ * @return Long A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Long longLessThan(Long toMatch)
+ {
+ return longLessThan(toMatch, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches longLessThan
+ * @param toMatch The number to be compared
+ * @param inclusive Whether or not a number equal to toMatch should be considered a match
+ * @return Long A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Long longLessThan(Long toMatch, Boolean inclusive)
+ {
+ return (Long)matches(new fflib_MatcherDefinitions.DecimalLessThan(toMatch, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches longMoreThan (not inclusive)
+ * @param toMatch The number to be compared
+ * @return Long A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Long longMoreThan(Long toMatch)
+ {
+ return longMoreThan(toMatch, false);
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches longMoreThan
+ * @param toMatch The number to be compared
+ * @param inclusive Whether or not a number equal to toMatch should be considered a match
+ * @return Long A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static Long longMoreThan(Long toMatch, Boolean inclusive)
+ {
+ return (Long)matches(new fflib_MatcherDefinitions.DecimalMoreThan(toMatch, inclusive));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an SObject of specified SObjectType
+ * @param objectType The SObjectType to be compared
+ * @return SObject a dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked
+ */
+ public static SObject sObjectOfType(Schema.SObjectType objectType)
+ {
+ return (SObject)matches(new fflib_MatcherDefinitions.SObjectOfType(objectType));
+ }
+ /**
+ * Registers a matcher which will check if the method is called with an SObject
+ * @param toMatch A Map of SObjectFields to required values, to be compared to concrete SObject records
+ * @return SObject a dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked
+ */
+ public static SObject sObjectWith(Map toMatch)
+ {
+ return (SObject)matches(new fflib_MatcherDefinitions.SObjectWith(toMatch));
+ }
+ /**
+ * Registers a matcher which will check if the method is called with a list of SObject
+ * @param toMatch A list of Map of SObjectFields to required values, to be compared to concrete SObject records
+ * @return SObject a dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked
+ */
+ public static SObject[] sObjectsWith(list> toMatch)
+ {
+ return (SObject[])matches(new fflib_MatcherDefinitions.SObjectsWith(toMatch,true));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with a list of SObject
+ * @param toMatch A list of Map of SObjectFields to required values, to be compared to concrete SObject records. Comparison can be order dependent
+ * @return SObject a dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked
+ */
+ public static SObject[] sObjectsWith(list> toMatch, Boolean matchInOrder)
+ {
+ return (SObject[])matches(new fflib_MatcherDefinitions.SObjectsWith(toMatch,matchInOrder));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an SObject
+ * @param toMatch The Id to be compared
+ * @return SObject a dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked
+ */
+ public static SObject sObjectWithId(Id toMatch)
+ {
+ return (SObject)matches(new fflib_MatcherDefinitions.SObjectWithId(toMatch));
+ }
+ /**
+ * Registers a matcher which will check if the method is called with an SObject
+ * @param toMatch The name to be compared
+ * @return SObject a dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked
+ */
+ public static SObject sObjectWithName(String toMatch)
+ {
+ return (SObject)matches(new fflib_MatcherDefinitions.SObjectWithName(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches stringContains
+ * @param toMatch The substring to be compared
+ * @return String A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static String stringContains(String toMatch)
+ {
+ return (String)matches(new fflib_MatcherDefinitions.StringContains(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches stringEndsWith
+ * @param toMatch The substring to be compared
+ * @return String A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static String stringEndsWith(String toMatch)
+ {
+ return (String)matches(new fflib_MatcherDefinitions.StringEndsWith(toMatch));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches stringIsBlank
+ * @return String A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static String stringIsBlank()
+ {
+ return (String)matches(new fflib_MatcherDefinitions.StringIsBlank());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches stringIsNotBlank
+ * @return String A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static String stringIsNotBlank()
+ {
+ return (String)matches(new fflib_MatcherDefinitions.StringIsNotBlank());
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches stringMatches
+ * @param regEx The regex String to be compared
+ * @return String A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static String stringMatches(String regEx)
+ {
+ return (String)matches(new fflib_MatcherDefinitions.StringMatches(regEx));
+ }
+
+ /**
+ * Registers a matcher which will check if the method is called with an arg that matches stringStartsWith
+ * @param toMatch The substring to be compared
+ * @return String A dummy object of the correct type, so that when called inline as part of a method call, the correct method is invoked.
+ */
+ public static String stringStartsWith(String toMatch)
+ {
+ return (String)matches(new fflib_MatcherDefinitions.StringStartsWith(toMatch));
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_Match.cls-meta.xml b/sfdx-source/apex-mocks/fflib_Match.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_Match.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_MatcherDefinitions.cls b/sfdx-source/apex-mocks/fflib_MatcherDefinitions.cls
new file mode 100644
index 00000000000..fa7a911ced4
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MatcherDefinitions.cls
@@ -0,0 +1,1390 @@
+/**
+ * Copyright (c) 2014-2017, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Class providing Apex Mocks standard matcher implementations.
+ * You shouldn't need to reference the classes directly outside of the ApexMocks framework, instead use the equivalent helper method in fflib_Match
+ * to construct the matcher, register the matcher and return an object of the correct type to be called in your unit test.
+ * E.g. Don't construct Eq(Object toMatch), instead call fflib_Match.eq(Object toMatch).
+ */
+public with sharing class fflib_MatcherDefinitions
+{
+ /**
+ * Connective - Enum representing the possible operators for the Combined matcher. Possible values: ALL, AT_LEAST_ONE, NONE
+ */
+ public Enum Connective
+ {
+ ALL,
+ AT_LEAST_ONE,
+ NONE
+ }
+
+ /*
+ * COMBINED MATCHER
+ */
+
+ /**
+ * Combined matcher: compares the supplied argument matches one, all or none of the internal matchers
+ */
+ public class Combined implements fflib_IMatcher
+ {
+ private Connective connectiveExpression;
+ private List internalMatchers;
+
+ /**
+ * Combined constructor
+ * @param connectiveExpression Controls the combination mode, i.e. if we need to match all, any or none of the inner matchers
+ * @param internalMatchers An ordered list of the internal matchers to be combined
+ * @return fflib_MatcherDefinitions.Combined A new Combined instance
+ */
+ public Combined(Connective connectiveExpression, List internalMatchers)
+ {
+ this.connectiveExpression = validate(connectiveExpression);
+ this.internalMatchers = validate(internalMatchers);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ for (fflib_IMatcher internalMatcher : internalMatchers)
+ {
+ if (internalMatcher.matches(arg))
+ {
+ if (connectiveExpression == Connective.AT_LEAST_ONE)
+ {
+ //At least one match => success!
+ return true;
+ }
+ else if (connectiveExpression == Connective.NONE)
+ {
+ //At least one match => failure!
+ return false;
+ }
+ }
+ else if (connectiveExpression == Connective.ALL)
+ {
+ //At least one mismatch => failure!
+ return false;
+ }
+ }
+
+ //We didn't return early.
+ //If matching any, must have been no matches => failure!
+ //If matching all, must have been all matches => success!
+ //If matching none, must have been all mismatches => success!
+ return connectiveExpression != Connective.AT_LEAST_ONE;
+ }
+
+ private Connective validate(Connective connectiveExpression)
+ {
+ if (connectiveExpression == null)
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Invalid connective expression: ' + connectiveExpression);
+ }
+
+ return connectiveExpression;
+ }
+
+ private List validate(List innerMatchers)
+ {
+ if (innerMatchers == null || innerMatchers.isEmpty())
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Invalid inner matchers: ' + innerMatchers);
+ }
+
+ return innerMatchers;
+ }
+
+ public override String toString()
+ {
+ List internalDescriptions = new List();
+ for (fflib_IMatcher internalMatcher : internalMatchers)
+ {
+ internalDescriptions.add('' + internalMatcher);
+ }
+ String internalDescription = String.join(internalDescriptions, ', ');
+
+ switch on connectiveExpression
+ {
+ when AT_LEAST_ONE
+ {
+ return '[any of: ' + internalDescription + ']';
+ }
+ when ALL
+ {
+ return '[all of: ' + internalDescription + ']';
+ }
+ when else
+ {
+ return '[none of: ' + internalDescription + ']';
+ }
+ }
+ }
+ }
+
+ /*
+ * OBJECT MATCHERS
+ */
+
+ /**
+ * Eq matcher: checks if the supplied argument is equal (==) to a specified object
+ */
+ public class Eq implements fflib_IMatcher
+ {
+ private Object toMatch;
+
+ /**
+ * Eq constructor
+ * @param toMatch The object to be compared
+ * @return fflib_MatcherDefinitions.Eq A new Eq instance
+ */
+ public Eq(Object toMatch)
+ {
+ this.toMatch = validateNotNull(toMatch);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ return toMatch == arg;
+ }
+
+ public override String toString()
+ {
+ return '[equals ' + stringify(toMatch) + ']';
+ }
+ }
+
+ /**
+ * RefEq matcher: checks if the supplied argument is a reference to the same object (===) as a specified object
+ */
+ public class RefEq implements fflib_IMatcher
+ {
+ private Object toMatch;
+
+ /**
+ * RefEq constructor
+ * @param toMatch The object to be compared
+ * @return fflib_MatcherDefinitions.RefEq A new RefEq instance
+ */
+ public RefEq(Object toMatch)
+ {
+ this.toMatch = validateNotNull(toMatch);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ return toMatch === arg;
+ }
+
+ public override String toString()
+ {
+ return '[reference equals ' + fflib_MatcherDefinitions.stringify(toMatch) + ']';
+ }
+ }
+
+ /*
+ * ANY MATCHERS
+ */
+
+ /**
+ * AnyBoolean matcher: checks if the supplied argument is an instance of a Boolean
+ */
+ public class AnyBoolean implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Boolean;
+ }
+
+ public override String toString()
+ {
+ return '[any Boolean]';
+ }
+ }
+
+ /**
+ * AnyDate matcher: checks if the supplied argument is an instance of a Date
+ */
+ public class AnyDate implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Date;
+ }
+
+ public override String toString()
+ {
+ return '[any Date]';
+ }
+ }
+
+ /**
+ * AnyDatetime matcher: checks if the supplied argument is an instance of a Datetime
+ */
+ public class AnyDatetime implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Datetime;
+ }
+
+ public override String toString()
+ {
+ return '[any DateTime]';
+ }
+ }
+
+ /**
+ * AnyDecimal matcher: checks if the supplied argument is an instance of a Decimal
+ */
+ public class AnyDecimal implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Decimal;
+ }
+
+ public override String toString()
+ {
+ return '[any Decimal]';
+ }
+ }
+
+ /**
+ * AnyDouble matcher: checks if the supplied argument is an instance of a Double
+ */
+ public class AnyDouble implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Double;
+ }
+
+ public override String toString()
+ {
+ return '[any Double]';
+ }
+ }
+
+ /**
+ * AnyFieldSet matcher: checks if the supplied argument is an instance of a FieldSet
+ */
+ public class AnyFieldSet implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Schema.FieldSet;
+ }
+
+ public override String toString()
+ {
+ return '[any FieldSet]';
+ }
+ }
+
+ /**
+ * AnyId matcher: checks if the supplied argument is an instance of an Id
+ */
+ public class AnyId implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Id;
+ }
+
+ public override String toString()
+ {
+ return '[any Id]';
+ }
+ }
+
+ /**
+ * AnyInteger matcher: checks if the supplied argument is an instance of an Integer
+ */
+ public class AnyInteger implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Integer;
+ }
+
+ public override String toString()
+ {
+ return '[any Integer]';
+ }
+ }
+
+ /**
+ * AnyList matcher: checks if the supplied argument is an instance of a List
+ */
+ public class AnyList implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof List;
+ }
+
+ public override String toString()
+ {
+ return '[any list]';
+ }
+ }
+
+ /**
+ * AnyLong matcher: checks if the supplied argument is an instance of a Long
+ */
+ public class AnyLong implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof Long;
+ }
+
+ public override String toString()
+ {
+ return '[any Long]';
+ }
+ }
+
+ /**
+ * AnyObject matcher: checks if the supplied argument is an instance of an Object
+ */
+ public class AnyObject implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null;
+ }
+
+ public override String toString()
+ {
+ return '[any Object]';
+ }
+ }
+
+ /**
+ * AnyString matcher: checks if the supplied argument is an instance of a String
+ */
+ public class AnyString implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof String;
+ }
+
+ public override String toString()
+ {
+ return '[any String]';
+ }
+ }
+
+ /**
+ * AnySObject matcher: checks if the supplied argument is an instance of an SObject
+ */
+ public class AnySObject implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof SObject;
+ }
+
+ public override String toString()
+ {
+ return '[any SObject]';
+ }
+ }
+
+ /**
+ * AnySObjectField matcher: checks if the supplied argument is an instance of an SObjectField
+ */
+ public class AnySObjectField implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof SObjectField;
+ }
+
+ public override String toString()
+ {
+ return '[any SObjectField]';
+ }
+ }
+
+ /**
+ * AnySObjectType matcher: checks if the supplied argument is an instance of an SObjectType
+ */
+ public class AnySObjectType implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof SObjectType;
+ }
+
+ public override String toString()
+ {
+ return '[any SObjectType]';
+ }
+ }
+
+ /*
+ * DATETIME MATCHERS
+ */
+
+ /**
+ * DatetimeAfter matcher: checks if the supplied argument is after a specified datetime
+ */
+ public class DatetimeAfter implements fflib_IMatcher
+ {
+ private Datetime fromDatetime;
+ private Boolean inclusive;
+
+ /**
+ * DatetimeAfter constructor
+ * @param fromDatetime The datetime to be compared
+ * @param inclusive Whether or not dates equal to the fromDatetime should be considered a match
+ * @return fflib_MatcherDefinitions.DatetimeAfter A new DatetimeAfter instance
+ */
+ public DatetimeAfter(Datetime fromDatetime, Boolean inclusive)
+ {
+ this.fromDatetime = (Datetime)validateNotNull(fromDatetime);
+ this.inclusive = (Boolean)validateNotNull(inclusive);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg instanceof Datetime)
+ {
+ Datetime datetimeToCompare = (Datetime)arg;
+ return inclusive ? fromDatetime <= datetimeToCompare : fromDatetime < datetimeToCompare;
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ if (inclusive)
+ {
+ return '[on or after ' + JSON.serialize(fromDateTime) + ']';
+ }
+ else
+ {
+ return '[after ' + JSON.serialize(fromDateTime) + ']';
+ }
+ }
+ }
+
+ /**
+ * DatetimeBefore matcher: checks if the supplied argument is before a specified datetime
+ */
+ public class DatetimeBefore implements fflib_IMatcher
+ {
+ private Datetime toDatetime;
+ private Boolean inclusive;
+
+ /**
+ * DatetimeBefore constructor
+ * @param toDatetime The datetime to be compared
+ * @param inclusive Whether or not dates equal to the toDatetime should be considered a match
+ * @return fflib_MatcherDefinitions.DatetimeBefore A new DatetimeBefore instance
+ */
+ public DatetimeBefore(Datetime toDatetime, Boolean inclusive)
+ {
+ this.toDatetime = (Datetime)validateNotNull(toDatetime);
+ this.inclusive = (Boolean)validateNotNull(inclusive);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg instanceof Datetime)
+ {
+ Datetime datetimeToCompare = (Datetime)arg;
+ return inclusive ? datetimeToCompare <= toDatetime : datetimeToCompare < toDatetime;
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ if (inclusive)
+ {
+ return '[on or before ' + JSON.serialize(toDateTime) + ']';
+ }
+ else
+ {
+ return '[before ' + JSON.serialize(toDateTime) + ']';
+ }
+ }
+ }
+
+ /**
+ * DatetimeBetween matcher: checks if the supplied argument is between two specified datetimes
+ */
+ public class DatetimeBetween implements fflib_IMatcher
+ {
+ private Datetime fromDatetime;
+ private Boolean inclusiveFrom;
+ private Datetime toDatetime;
+ private Boolean inclusiveTo;
+
+ /**
+ * DatetimeBetween constructor
+ * @param fromDatetime The lower bound datetime to be compared
+ * @param inclusiveFrom Whether or not dates equal to the fromDatetime should be considered a match
+ * @param toDatetime The upper bound dateetime to be compared
+ * @param inclusiveTo Whether or not dates equal to the toDatetime should be considered a match
+ * @return fflib_MatcherDefinitions.DatetimeBetween A new DatetimeBetween instance
+ */
+ public DatetimeBetween(Datetime fromDatetime, Boolean inclusiveFrom, Datetime toDatetime, Boolean inclusiveTo)
+ {
+ this.fromDatetime = (Datetime)validateNotNull(fromDatetime);
+ this.inclusiveFrom = (Boolean)validateNotNull(inclusiveFrom);
+ this.toDatetime = (Datetime)validateNotNull(toDatetime);
+ this.inclusiveTo = (Boolean)validateNotNull(inclusiveTo);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg instanceof Datetime)
+ {
+ Datetime datetimeToCompare = (Datetime)arg;
+ if ((inclusiveFrom ? datetimeToCompare >= fromDatetime : datetimeToCompare > fromDatetime)
+ && (inclusiveTo ? datetimeToCompare <= toDatetime : datetimeToCompare < toDatetime))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ return String.format('[{0} {1} and {2} {3}]', new List{
+ inclusiveFrom ? 'on or after' : 'after',
+ JSON.serialize(fromDateTime),
+ inclusiveTo ? 'on or before' : 'before',
+ JSON.serialize(toDateTime)
+ });
+ }
+ }
+
+ /*
+ * DECIMAL (AND OTHER NUMBER) MATCHERS
+ */
+
+ /**
+ * DecimalBetween matcher: checks if the supplied argument is between two specified decimals
+ */
+ public class DecimalBetween implements fflib_IMatcher
+ {
+ private Decimal lower;
+ private Boolean inclusiveLower;
+ private Decimal upper;
+ private Boolean inclusiveUpper;
+
+ /**
+ * DecimalBetween constructor
+ * @param lower The lower bound number to be compared
+ * @param inclusiveLower Whether or not numbers equal to lower should be considered a match
+ * @param upper The upper bound number to be compared
+ * @param inclusiveUpper Whether or not numbers equal to upper should be considered a match
+ * @return fflib_MatcherDefinitions.DecimalBetween A new DecimalBetween instance
+ */
+ public DecimalBetween(Decimal lower, Boolean inclusiveLower, Decimal upper, Boolean inclusiveUpper)
+ {
+ this.lower = (Decimal)validateNotNull(lower);
+ this.inclusiveLower = (Boolean)validateNotNull(inclusiveLower);
+ this.upper = (Decimal)validateNotNull(upper);
+ this.inclusiveUpper = (Boolean)validateNotNull(inclusiveUpper);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof Decimal)
+ {
+ Decimal longArg = (Decimal)arg;
+
+ if ((inclusiveLower ? longArg >= lower : longArg > lower)
+ && (inclusiveUpper ? longArg <= upper : longArg < upper))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ return String.format('{0} {1} and {2} {3}', new List{
+ inclusiveLower ? 'greater than or equal to' : 'greater than',
+ '' + lower,
+ inclusiveUpper ? 'less than or equal to' : 'less than',
+ '' + upper
+ });
+ }
+ }
+
+ /**
+ * DecimalLessThan matcher: checks if the supplied argument is less than a specified decimal
+ */
+ public class DecimalLessThan implements fflib_IMatcher
+ {
+ private Decimal toMatch;
+ private Boolean inclusive;
+
+ /**
+ * DecimalLessThan constructor
+ * @param toMatch The number to be compared against
+ * @param inclusive Whether or not numbers equal to toMatch should be considered a match
+ * @return fflib_MatcherDefinitions.DecimalLessThan A new DecimalLessThan instance
+ */
+ public DecimalLessThan(Decimal toMatch, Boolean inclusive)
+ {
+ this.toMatch = (Decimal)validateNotNull(toMatch);
+ this.inclusive = (Boolean)validateNotNull(inclusive);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof Decimal)
+ {
+ Decimal longArg = (Decimal)arg;
+ return inclusive ? longArg <= toMatch : longArg < toMatch;
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ if (inclusive)
+ {
+ return '[less than or equal to ' + toMatch + ']';
+ }
+ else
+ {
+ return '[less than ' + toMatch + ']';
+ }
+ }
+ }
+
+ /**
+ * DecimalMoreThan matcher: checks if the supplied argument is greater than a specified decimal
+ */
+ public class DecimalMoreThan implements fflib_IMatcher
+ {
+ private Decimal toMatch;
+ private Boolean inclusive;
+
+ /**
+ * DecimalMoreThan constructor
+ * @param toMatch The number to be compared against
+ * @param inclusive Whether or not numbers equal to toMatch should be considered a match
+ * @return fflib_MatcherDefinitions.DecimalMoreThan A new DecimalMoreThan instance
+ */
+ public DecimalMoreThan(Decimal toMatch, Boolean inclusive)
+ {
+ this.toMatch = (Decimal)validateNotNull(toMatch);
+ this.inclusive = (Boolean)validateNotNull(inclusive);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof Decimal)
+ {
+ Decimal longArg = (Decimal)arg;
+ return inclusive ? longArg >= toMatch : longArg > toMatch;
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ if (inclusive)
+ {
+ return '[greater than or equal to ' + toMatch + ']';
+ }
+ else
+ {
+ return '[greater than ' + toMatch + ']';
+ }
+ }
+ }
+
+ /**
+ * FIELDSET MATCHERS
+ */
+
+ /**
+ * FieldSetEquivalentTo matcher: checks the supplied argument is a field set with the same field set members as a specified field set
+ * This matcher is needed because equivalent FieldSets do not pass == checks, and we can't override equals/hashcode on FieldSets.
+ */
+ public class FieldSetEquivalentTo implements fflib_IMatcher
+ {
+ private final Set toMatch;
+
+ /*
+ * Dirty test-only constructor, allowing us to test this class even if there are no field sets defined in the current org.
+ */
+ @TestVisible
+ public FieldSetEquivalentTo()
+ {
+ this.toMatch = null;
+ }
+
+ public FieldSetEquivalentTo(Schema.FieldSet toMatch)
+ {
+ this.toMatch = new Set(((Schema.FieldSet)validateNotNull(toMatch)).getFields());
+ }
+
+ public Boolean matches(Object arg)
+ {
+ return (toMatch != null && arg != null && arg instanceof Schema.FieldSet) ? toMatch == new Set(((FieldSet)arg).getFields()) : false;
+ }
+
+ public override String toString()
+ {
+ return '[FieldSet with fields ' + fflib_MatcherDefinitions.stringify(toMatch) + ']';
+ }
+ }
+
+ /*
+ * IS MATCHERS
+ */
+
+ /**
+ * IsNotNull matcher: checks the supplied argument is not null
+ */
+ public class IsNotNull implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null;
+ }
+
+ public override String toString()
+ {
+ return '[is not null]';
+ }
+ }
+
+ /**
+ * IsNull matcher: checks the supplied argument is null
+ */
+ public class IsNull implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg == null;
+ }
+
+ public override String toString()
+ {
+ return '[is null]';
+ }
+ }
+
+ /*
+ * LIST MATCHERS
+ */
+
+ /**
+ * ListContains matcher: checks if the supplied argument is equal (==) to any of the elements in a specified list
+ */
+ public class ListContains implements fflib_IMatcher
+ {
+ private Object toMatch;
+
+ /**
+ * ListContains constructor
+ * @param toMatch The list of objects to be compared
+ * @return fflib_MatcherDefinitions.ListContains A new ListContains instance
+ */
+ public ListContains(Object toMatch)
+ {
+ this.toMatch = toMatch;
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof List)
+ {
+ for (Object o : (List)arg)
+ {
+ if (o == toMatch)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ return '[list containing ' + fflib_MatcherDefinitions.stringify(toMatch) + ']';
+ }
+ }
+
+ /**
+ * ListIsEmpty matcher: checks if the supplied argument is an empty list
+ */
+ public class ListIsEmpty implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg != null
+ && arg instanceof List
+ && ((List)arg).isEmpty();
+ }
+
+ public override String toString()
+ {
+ return '[empty list]';
+ }
+ }
+
+ /*
+ * SOBJECT MATCHERS
+ */
+
+ /**
+ * SObjectOfType matcher: checks if the supplied argument has the specified SObjectType
+ */
+ public class SObjectOfType implements fflib_IMatcher
+ {
+ private Schema.SObjectType objectType;
+
+ /**
+ * SObjectOfType constructor
+ * @param objectType The SObjectType to be compared
+ * @return fflib_MatcherDefinitions.SObjectOfType A new SObjectOfType instance
+ */
+ public SObjectOfType(Schema.SObjectType objectType)
+ {
+ this.objectType = (Schema.SObjectType)validateNotNull(objectType);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof SObject)
+ {
+ SObject soArg = (SObject)arg;
+ return soArg.getSObjectType() == objectType;
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ return '[SObject of type ' + objectType + ']';
+ }
+ }
+
+ /**
+ * SObjectWith matcher: compares the supplied argument against a Map, representing fields and their expected values.
+ * Note. this method silently catches exceptions getting values for the supplied fields from the arguments supplied in method calls.
+ *
+ * If your matcher is mysteriously failing for your SObject record, it may be getting silent 'SObject row was retrieved via SOQL without querying
+ * the requested field' exceptions, because you haven't queried all of the fields used in this matcher.
+ */
+ public class SObjectWith implements fflib_IMatcher
+ {
+ private Map toMatch;
+
+ /**
+ * SObjectWith constructor
+ * @param toMatch A map of fields to their values to be compared. We compare each of these fields against the supplied sobject's field values.
+ * @return fflib_MatcherDefinitions.SObjectWith A new SObjectWith instance
+ */
+ public SObjectWith(Map toMatch)
+ {
+ this.toMatch = validate(toMatch);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof SObject)
+ {
+ SObject soArg = (SObject)arg;
+ if (!sobjectMatches(soArg,this.toMatch))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private Map validate(Map arg)
+ {
+ if (arg == null || arg.isEmpty())
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Arg cannot be null/empty: ' + arg);
+ }
+
+ return arg;
+ }
+
+ public override String toString()
+ {
+ return '[SObject with fields ' + fflib_MatcherDefinitions.stringify(toMatch) + ']';
+ }
+ }
+
+ /**
+ * SObjectsWith matcher: compares the supplied list argument against a list>, representing fields and their expected values.
+ * Each list element represents one Sobject in a list supplied to a mocked method that accepts list.
+ * Each list element that is a map is compared against the equivalent argument list element position
+ *
+ * Example:
+ * You use uow.registerNew(someListofAccounts). You mock uow in the testmethod.
+ * toMatch is new list {
+ * new map {Account.Name => 'foo'},
+ * new map {Account.Name => 'bar'}
+ * }
+ * By default, matchers compare against argument elements in order, viz:
+ * The matcher will compare the first Account in the list passed to uow.registerNew to the first map of field values (i.e. Account[0].Name must be 'foo')
+ * The matcher then compares the second Account in the list passed to uow.registerNew to the second map of field values (i.e. Account[1].Name must be 'bar')
+ *
+ * Optional second argument matchInOrderr if false means that each argument element is compared against all matcher elements
+ * if everuy argument is matched exactly once and no matcher matches more than once, then the match is true
+ *
+ * If the arity of the list passed in the mocked method doesn't agree with the arity of the map of expected field values, false is returned
+ *
+ * Note. this method silently catches exceptions getting values for the supplied fields from the arguments supplied in method calls.
+ *
+ * If your matcher is mysteriously failing for your SObject record, it may be getting silent 'SObject row was retrieved via SOQL without querying
+ * the requested field' exceptions, because you haven't queried all of the fields used in this matcher.
+ */
+ public class SObjectsWith implements fflib_IMatcher
+ {
+ private list> toMatch;
+ private Boolean matchInOrder {
+ get
+ {
+ return matchInOrder == null ? false : matchInOrder;
+ }
+ set;
+ }
+
+ /**
+ * SObjectsWith constructor
+ * @param toMatch A list of maps of fields to their values to be compared. We compare each of these fields against the supplied list of sobject's field values.
+ * @return fflib_MatcherDefinitions.SObjectWith A new SObjectWith instance
+ */
+ public SObjectsWith(list> toMatch, Boolean matchInOrder)
+ {
+ this.toMatch = validate(toMatch);
+ this.matchInOrder = matchInOrder;
+ }
+ public SObjectsWith(list> toMatch)
+ {
+ this.toMatch = validate(toMatch);
+ this.matchInOrder = true;
+ }
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof list)
+ {
+ SObject[] sobjsArg = (SObject[])arg;
+ list> toMatches = new list>();
+
+ // Counters for matchInOrder = false; not relevant for matchInOrder = true
+ list argMatchedCounts = new list(); // # times matched by a matcher. anything other than 1 is match error
+ list matcherMatchedCounts = new list(); // for each map
+ // # args that match it. Anything other than 1 is match error
+
+
+ for (map mtchElm : toMatch)
+ {
+ toMatches.add(mtchElm);
+ matcherMatchedCounts.add(0);
+ }
+
+ if (sobjsArg.size() != toMatches.size()) // arity of arguments to mocked method doesn't agree with arity of expected matches
+ {
+ return false;
+ }
+
+ if (matchInOrder)
+ {
+ for (Integer i = 0; i < sobjsArg.size(); i++)
+ { // match in order (toMatch[i] must match arg[i])
+ if (!sobjectMatches(sobjsArg[i],toMatches[i]))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ // match in any order (but every arg must be matched only once)
+ for (Integer i = 0; i < sobjsArg.size(); i++)
+ {
+ argMatchedCounts.add(0);
+ // For each arg passed to mocked method, see if any match in the list of match field maps.
+ // Loop within loop so not hugely efficient but there are no IDs to rely on.
+ // Avoid unit test methods that build huge lists of expected results
+
+ for (Integer m = 0; m < toMatches.size(); m++)
+ {
+ if (sobjectMatches(sobjsArg[i],toMatches[m]))
+ {
+ argMatchedCounts[i] ++;
+ matcherMatchedCounts[m] ++;
+ }
+ }
+ }
+ // Check to see that every arg was matched only once
+ // Check to see that every matcher matched only once
+ // Anything else is a match fail
+
+ for (Integer i=0; i < argMatchedCounts.size(); i++)
+ {
+ if (argMatchedCounts[i] != 1 || matcherMatchedCounts[i] != 1)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private list> validate(list> arg)
+ {
+ if (arg == null || arg.isEmpty() )
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Arg cannot be null/empty/other than list of map: ' + arg);
+ }
+
+ return arg;
+ }
+
+ public override String toString()
+ {
+ if (matchInOrder)
+ {
+ return '[ordered SObjects with ' + fflib_MatcherDefinitions.stringify(toMatch) + ']';
+ }
+ else
+ {
+ return '[unordered SObjects with ' + fflib_MatcherDefinitions.stringify(toMatch) + ']';
+ }
+ }
+
+
+ }
+
+ /**
+ * helper for the sObjectWith, sObjectsWith matchers
+ * Compares to see if the field values in toMatch exist in the sobj
+ **/
+ private static Boolean sObjectMatches(Sobject sobj, map toMatch)
+ {
+ for (Schema.SObjectField f : toMatch.keySet())
+ {
+ Object valueToMatch = toMatch.get(f);
+
+ try
+ {
+ if (sobj.get(f) != valueToMatch)
+ {
+ return false;
+ }
+ }
+ catch (Exception e)
+ {
+ //If we fail to get the value for a field it's either:
+ // - 'SObject row was retrieved via SOQL without querying the requested field' as a mismatch
+ // - System.SObjectException - Account.Id does not belong to SObject type Opportunity
+ //Don't care too much, just treat this as a mismatch.
+ return false;
+ }
+ }
+ return true; // map of expected fieldvals found in sobj arg
+ }
+
+ /**
+ * SObjectWithId matcher: checks if the supplied argument has the specified Id
+ */
+ public class SObjectWithId implements fflib_IMatcher
+ {
+ private Id toMatch;
+
+ /**
+ * SObjectWithId constructor
+ * @param toMatch The Id to be compared
+ * @return fflib_MatcherDefinitions.SObjectWithId A new SObjectWithId instance
+ */
+ public SObjectWithId(Id toMatch)
+ {
+ this.toMatch = (Id)validateNotNull(toMatch);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof SObject)
+ {
+ SObject soArg = (SObject)arg;
+ return soArg.Id == toMatch;
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ return '[SObject with Id "' + toMatch + '"]';
+ }
+ }
+
+ /**
+ * SObjectWithName matcher: checks if the supplied argument has the specified Name
+ */
+ public class SObjectWithName implements fflib_IMatcher
+ {
+ private String toMatch;
+
+ /**
+ * SObjectWithName constructor
+ * @param toMatch The name to be compared
+ * @return fflib_MatcherDefinitions.SObjectWithName A new SObjectWithName instance
+ */
+ public SObjectWithName(String toMatch)
+ {
+ this.toMatch = (String)validateNotNull(toMatch);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ if (arg != null && arg instanceof SObject)
+ {
+ SObject soArg = (SObject)arg;
+ Schema.DescribeSObjectResult describe = soArg.getSObjectType().getDescribe();
+ for (Schema.SObjectField f : describe.fields.getMap().values())
+ {
+ if (f.getDescribe().isNameField())
+ {
+ return soArg.get(f) == toMatch;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public override String toString()
+ {
+ return '[SObject with Name "' + toMatch + '"]';
+ }
+ }
+
+ /*
+ * STRING MATCHERS
+ */
+
+ /**
+ * StringContains matcher: checks if the supplied argument contains the specified substring
+ */
+ public class StringContains implements fflib_IMatcher
+ {
+ private String toMatch;
+
+ /**
+ * StringContains constructor
+ * @param toMatch The substring to be compared
+ * @return fflib_MatcherDefinitions.StringContains A new StringContains instance
+ */
+ public StringContains(String toMatch)
+ {
+ this.toMatch = (String)validateNotNull(toMatch);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof String ? ((String)arg).contains(toMatch) : false;
+ }
+
+ public override String toString()
+ {
+ return '[contains "' + toMatch + '"]';
+ }
+ }
+
+ /**
+ * StringEndsWith matcher: checks if the supplied argument ends with the specified substring
+ */
+ public class StringEndsWith implements fflib_IMatcher
+ {
+ private String toMatch;
+
+ /**
+ * StringEndsWith constructor
+ * @param toMatch The substring to be compared
+ * @return fflib_MatcherDefinitions.StringEndsWith A new StringEndsWith instance
+ */
+ public StringEndsWith(String toMatch)
+ {
+ this.toMatch = (String)validateNotNull(toMatch);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof String ? ((String)arg).endsWith(toMatch) : false;
+ }
+
+ public override String toString()
+ {
+ return '[ends with "' + toMatch + '"]';
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ public override String toString()
+ {
+ return '[blank String]';
+ }
+ }
+
+ /**
+ * 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 != NULL && arg instanceof String) ? String.isNotBlank((String)arg) : false;
+ }
+
+ public override String toString()
+ {
+ return '[non-blank String]';
+ }
+ }
+
+ /**
+ * StringMatches matcher: checks if the supplied argument matches the specified regex expression
+ */
+ public class StringMatches implements fflib_IMatcher
+ {
+ private Pattern pat;
+ private final String regEx;
+
+ /**
+ * StringMatches constructor
+ * @param toMatch The substring to be compared
+ * @return fflib_MatcherDefinitions.StringMatches A new StringMatches instance
+ */
+ public StringMatches(String regEx)
+ {
+ this.regEx = regEx;
+ this.pat = Pattern.compile((String)validateNotNull(regEx));
+ }
+
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof String ? pat.matcher((String)arg).matches() : false;
+ }
+
+ public override String toString()
+ {
+ return '[matches regex "' + regEx + '"]';
+ }
+ }
+
+ /**
+ * StringStartsWith matcher: checks if the supplied argument starts with the specified substring
+ */
+ public class StringStartsWith implements fflib_IMatcher
+ {
+ private String toMatch;
+
+ /**
+ * StringStartsWith constructor
+ * @param toMatch The substring to be compared
+ * @return fflib_MatcherDefinitions.StringStartsWith A new StringStartsWith instance
+ */
+ public StringStartsWith(String toMatch)
+ {
+ this.toMatch = (String)validateNotNull(toMatch);
+ }
+
+ public Boolean matches(Object arg)
+ {
+ return arg != null && arg instanceof String ? ((String)arg).startsWith(toMatch) : false;
+ }
+
+ public override String toString()
+ {
+ return '[starts with "' + toMatch + '"]';
+ }
+ }
+
+ /*
+ * Helpers
+ */
+
+ private static Object validateNotNull(Object arg)
+ {
+ if (arg == null)
+ {
+ throw new fflib_ApexMocks.ApexMocksException('Arg cannot be null: ' + arg);
+ }
+
+ return arg;
+ }
+
+ public static String stringify(Object value)
+ {
+ try
+ {
+ return JSON.serialize(value, false);
+ }
+ catch (Exception error)
+ {
+ return '' + value;
+ }
+ }
+}
diff --git a/sfdx-source/apex-mocks/fflib_MatcherDefinitions.cls-meta.xml b/sfdx-source/apex-mocks/fflib_MatcherDefinitions.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MatcherDefinitions.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_MatchersReturnValue.cls b/sfdx-source/apex-mocks/fflib_MatchersReturnValue.cls
new file mode 100644
index 00000000000..b6984c2678b
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MatchersReturnValue.cls
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2014-2016, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public with sharing class fflib_MatchersReturnValue
+{
+ public List matchers;
+ public fflib_MethodReturnValue returnValue;
+
+ public fflib_MatchersReturnValue(List matchers, fflib_MethodReturnValue returnValue)
+ {
+ this.matchers = matchers;
+ this.returnValue = returnValue;
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_MatchersReturnValue.cls-meta.xml b/sfdx-source/apex-mocks/fflib_MatchersReturnValue.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MatchersReturnValue.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_MethodArgValues.cls b/sfdx-source/apex-mocks/fflib_MethodArgValues.cls
new file mode 100644
index 00000000000..58857b885de
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodArgValues.cls
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2014-2016, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public with sharing class fflib_MethodArgValues
+{
+ public List argValues;
+
+ /**
+ * Wrapper object which encapsulates the argument values
+ * supplied during a given method call.
+ * @param argValues The
+ * @return fflib_MethodArgValues The method argument wrapper object
+ */
+ public fflib_MethodArgValues(List argValues)
+ {
+ this.argValues = argValues;
+ }
+
+ /**
+ * Standard equals override.
+ * @param other The object whose equality we are verifying
+ * @return Boolean True if meaningfully equivalent, false otherwise.
+ */
+ public Boolean equals(Object other)
+ {
+ if (this === other)
+ {
+ return true;
+ }
+
+ fflib_MethodArgValues that = other instanceof fflib_MethodArgValues ? (fflib_MethodArgValues)other : null;
+ return that != null && this.argValues == that.argValues;
+ }
+
+ /**
+ * Standard hashCode override.
+ * @return Integer The generated hashCode
+ */
+ public Integer hashCode()
+ {
+ Integer prime = 31;
+ Integer result = 1;
+
+ result = prime * result + ((argValues == null) ? 0 : argValues.hashCode());
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_MethodArgValues.cls-meta.xml b/sfdx-source/apex-mocks/fflib_MethodArgValues.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodArgValues.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_MethodCountRecorder.cls b/sfdx-source/apex-mocks/fflib_MethodCountRecorder.cls
new file mode 100644
index 00000000000..7bec4e5bc76
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodCountRecorder.cls
@@ -0,0 +1,61 @@
+/*
+ Copyright (c) 2014-2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * @group Core
+ */
+public with sharing class fflib_MethodCountRecorder
+{
+ /*
+ * Map of method arguments by type name.
+ *
+ * Key: qualifiedMethod
+ * Object: list of method arguments.
+ *
+ * Object: map of count by method call argument.
+ */
+ private static Map> methodArgumentsByTypeName =
+ new Map>();
+
+ private static List orderedMethodCalls =
+ new List();
+
+ /**
+ * Getter for the list of the methods ordered calls.
+ * @return The list of methods called in order.
+ */
+ public static List getOrderedMethodCalls()
+ {
+ return orderedMethodCalls;
+ }
+
+ /**
+ * Getter for the map of the method's calls with the related arguments.
+ * @return The map of methods called with the arguments.
+ */
+ public static Map> getMethodArgumentsByTypeName()
+ {
+ return methodArgumentsByTypeName;
+ }
+
+ /**
+ * Record a method was called on a mock object.
+ * @param invocation The object holding all the data of the invocation, like the method and arguments and the mock instance.
+ */
+ public void recordMethod(fflib_InvocationOnMock invocation)
+ {
+ List methodArgs =
+ methodArgumentsByTypeName.get(invocation.getMethod());
+
+ if (methodArgs == null)
+ {
+ methodArgs = new List();
+ methodArgumentsByTypeName.put(invocation.getMethod(), methodArgs);
+ }
+
+ methodArgs.add(invocation.getMethodArgValues());
+
+ orderedMethodCalls.add(invocation);
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_MethodCountRecorder.cls-meta.xml b/sfdx-source/apex-mocks/fflib_MethodCountRecorder.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodCountRecorder.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_MethodReturnValue.cls b/sfdx-source/apex-mocks/fflib_MethodReturnValue.cls
new file mode 100644
index 00000000000..7f834006add
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodReturnValue.cls
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2014-2017, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @group Core
+ * Class defining a method return value.
+ */
+@isTest
+public with sharing class fflib_MethodReturnValue
+{
+ private StandardAnswer basicAnswer = new StandardAnswer();
+
+ /**
+ * Instance of the implementation of the Answer interface that implements the answer,
+ * if an answer isn't explicitly set the standard answer will be used, which just returns the stubbed return value.
+ */
+ public fflib_Answer Answer { get; set; }
+
+ /**
+ * Setup a stubbed return value.
+ * @param value The value to return from the stubbed method call.
+ * @return The fflib_MethodReturnValue instance to allow you to chain the methods.
+ */
+ public fflib_MethodReturnValue thenReturn(Object value)
+ {
+ thenAnswer(this.basicAnswer.setValue(value));
+ return this;
+ }
+
+ /**
+ * Setup a stubbed exception.
+ * @param e The exception to throw from the stubbed method call.
+ * @return The fflib_MethodReturnValue instance to allow you to chain the methods.
+ */
+ public fflib_MethodReturnValue thenThrow(Exception e)
+ {
+ thenAnswer(this.basicAnswer.setValue(e));
+ return this;
+ }
+
+ /**
+ * Setup a stubbed answer.
+ * @param answer The answer to run from the stubbed method call.
+ */
+ public void thenAnswer(fflib_Answer answer)
+ {
+ this.Answer = answer;
+ }
+
+ /**
+ * Setup a list of stubbed return values.
+ * @param values The values to return from the stubbed method call in consecutive calls.
+ * @return The fflib_MethodReturnValue instance to allow you to chain the methods.
+ */
+ public fflib_MethodReturnValue thenReturnMulti(List values)
+ {
+ thenAnswer(this.basicAnswer.setValues(values));
+ return this;
+ }
+
+ /**
+ * Setup a list stubbed exceptions.
+ * @param es The exceptions to throw from the stubbed method call in consecutive calls.
+ * @return The fflib_MethodReturnValue instance to allow you to chain the methods.
+ */
+ public fflib_MethodReturnValue thenThrowMulti(List es)
+ {
+ thenAnswer(this.basicAnswer.setValues(es));
+ return this;
+ }
+
+ /**
+ * @group Core
+ * Inner class to handle all the stubs that do not use the thenAnswer method directly.
+ * For internal use only.
+ */
+ public class StandardAnswer implements fflib_Answer
+ {
+ private Integer whichStubReturnIndex = 0;
+ /*
+ * It stores the return values for the method stubbed.
+ * The values would be stored and then returned as part of the standard answer invocation.
+ */
+ private List ReturnValues = new List();
+
+ /**
+ * Setter of a single return value.
+ * @param value The value to be set as return value for the StandardAnswer object.
+ * @return The StandardAnswer instance.
+ */
+ public StandardAnswer setValue(Object value)
+ {
+ ReturnValues.add(value);
+ return this;
+ }
+
+ /**
+ * Setter of the list of return values.
+ * @param value The value to be set as return value for the StandardAnswer object.
+ * @return the StandardAnswer instance.
+ */
+ public StandardAnswer setValues(List values)
+ {
+ if(values == null || values.size() == 0)
+ {
+ throw new fflib_ApexMocks.ApexMocksException(
+ 'The stubbing is not correct, no return values have been set.');
+ }
+
+ ReturnValues.addAll(values);
+ return this;
+ }
+
+ /**
+ * Standard basic implementation for the fflib_Answer answer method, to be used as default answering.
+ * @param invocation The invocation to answer for.
+ * @return The ReturnValue for the method stubbed.
+ */
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ if(ReturnValues == null || ReturnValues.size() == 0)
+ {
+ throw new fflib_ApexMocks.ApexMocksException(
+ 'The stubbing is not correct, no return values have been set.');
+ }
+
+ Integer returnValuesSize = ReturnValues.size()-1;
+
+ if(whichStubReturnIndex < returnValuesSize)
+ {
+ return ReturnValues[whichStubReturnIndex++];
+ }
+ else
+ {
+ return ReturnValues[returnValuesSize];
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_MethodReturnValue.cls-meta.xml b/sfdx-source/apex-mocks/fflib_MethodReturnValue.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodReturnValue.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_MethodReturnValueRecorder.cls b/sfdx-source/apex-mocks/fflib_MethodReturnValueRecorder.cls
new file mode 100644
index 00000000000..fd9f37d5117
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodReturnValueRecorder.cls
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2014, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @group Core
+ */
+public with sharing class fflib_MethodReturnValueRecorder
+{
+ public Boolean Stubbing { get; set; }
+
+ public List DoThrowWhenExceptions { get; set; }
+
+ /**
+ * Map of matchers by method.
+ *
+ * Key: qualifiedMethod
+ * Object: map of method return values by method.
+ */
+
+ private Map> matcherReturnValuesByMethod;
+
+ public fflib_MethodReturnValue MethodReturnValue { get; private set; }
+
+ public fflib_MethodReturnValueRecorder()
+ {
+ matcherReturnValuesByMethod = new Map>();
+
+ MethodReturnValue = null;
+ }
+
+ /**
+ * Prepare a stubbed method return value.
+ * @param invocation The object holding all the data of the invocation, like the method and arguments and the mock instance.
+ * @return The MethodReturnValue instance.
+ */
+ public fflib_MethodReturnValue prepareMethodReturnValue(fflib_InvocationOnMock invocation)
+ {
+ MethodReturnValue = new fflib_MethodReturnValue();
+
+ List matcherReturnValues = matcherReturnValuesByMethod.get(invocation.getMethod());
+ if (matcherReturnValues == null)
+ {
+ matcherReturnValues = new List();
+ matcherReturnValuesByMethod.put(invocation.getMethod(), matcherReturnValues);
+ }
+
+ List argValues = invocation.getMethodArgValues().argValues;
+
+ //Register explicit arg values as 'equals' matchers, to preserve old behaviour
+ if (!fflib_Match.Matching)
+ {
+ for (Object arg : argValues)
+ {
+ if (arg == null)
+ fflib_Match.isNull();
+ else
+ fflib_Match.eq(arg);
+ }
+ }
+
+ List matchers = fflib_Match.getAndClearMatchers(argValues.size());
+ matcherReturnValues.add(new fflib_MatchersReturnValue(matchers, MethodReturnValue));
+
+ return MethodReturnValue;
+ }
+
+ /**
+ * Get the method return value for the given method call.
+ * @param invocation The object holding all the data of the invocation, like the method and arguments and the mock instance.
+ * @return The MethodReturnValue instance.
+ */
+ public fflib_MethodReturnValue getMethodReturnValue(fflib_InvocationOnMock invocation)
+ {
+ List matchersForMethods = matcherReturnValuesByMethod.get(invocation.getMethod());
+ if (matchersForMethods != null)
+ {
+ for (Integer i = matchersForMethods.size() - 1; i >= 0; i--)
+ {
+ fflib_MatchersReturnValue matchersReturnValue = (fflib_MatchersReturnValue)matchersForMethods.get(i);
+ if (fflib_Match.matchesAllArgs(invocation.getMethodArgValues(), matchersReturnValue.matchers))
+ {
+ return matchersReturnValue.ReturnValue;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Prepare a stubbed exceptions for a void method.
+ * @param exps The list of exception to throw.
+ */
+ public void prepareDoThrowWhenExceptions(List exps)
+ {
+ DoThrowWhenExceptions = exps;
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_MethodReturnValueRecorder.cls-meta.xml b/sfdx-source/apex-mocks/fflib_MethodReturnValueRecorder.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodReturnValueRecorder.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_MethodVerifier.cls b/sfdx-source/apex-mocks/fflib_MethodVerifier.cls
new file mode 100644
index 00000000000..38aca8e0f22
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodVerifier.cls
@@ -0,0 +1,154 @@
+/*
+ Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * This class implements the actual verification.
+ * @group Core
+ */
+public abstract class fflib_MethodVerifier
+{
+ /**
+ * Verify a method was called on a mock object.
+ * @param mockInvocation The object holding all the data of the invocation, like the method and arguments and the mock instance.
+ * @param verificationMode The verification mode that holds the setting about how the verification should be performed.
+ */
+ public void verifyMethodCall(fflib_InvocationOnMock mockInvocation, fflib_VerificationMode verificationMode)
+ {
+ validateMode(verificationMode);
+
+ verify(mockinvocation.getMethod(), mockinvocation.getMethodArgValues(), verificationMode);
+ }
+
+ /*
+ * Method that actually performs the verify
+ * @param qm The method to be verified.
+ * @param methodArg The arguments of the method that needs to be verified.
+ * @param verificationMode The verification mode that holds the setting about how the verification should be performed.
+ */
+ protected abstract void verify(
+ fflib_QualifiedMethod qm,
+ fflib_MethodArgValues methodArg,
+ fflib_VerificationMode verificationMode);
+
+ /*
+ * Method that validates the verification mode used in the verify.
+ * Not all the methods from the fflib_VerificationMode are implemented for the different classes that extends the fflib_MethodVerifier.
+ * The error is thrown at run time, so this method is called in the method that actually performs the verify.
+ * @param verificationMode The verification mode that has to have been verified.
+ * @throws Exception with message for the fflib_VerificationMode not implemented.
+ */
+ protected abstract void validateMode(fflib_VerificationMode verificationMode);
+
+ /*
+ * Method that performs the argument capturing.
+ * Captures argument values during verification.
+ * @param matchers The list of matcher with which a method is verified.
+ */
+ protected void capture(List matchers)
+ {
+ for(fflib_IMatcher matcher : matchers)
+ {
+ if( matcher instanceof fflib_ArgumentCaptor.AnyObject )
+ {
+ ((fflib_ArgumentCaptor.AnyObject)matcher).storeArgument();
+ }
+ }
+ }
+
+ protected void throwException(
+ fflib_QualifiedMethod qm,
+ String inOrder,
+ Integer expectedCount,
+ String qualifier,
+ Integer methodCount,
+ String customAssertMessage,
+ fflib_MethodArgValues expectedArguments,
+ List expectedMatchers,
+ List actualArguments)
+ {
+ String template = 'EXPECTED COUNT: {0}{1}{2}' // qualified expected count (e.g. "3 or fewer times in order")
+ + '\nACTUAL COUNT: {3}' // actual count
+ + '\nMETHOD: {4}' // method signature
+ + '{5}'; // custom assert message
+
+ String expectedDescription = '';
+ String actualDescription = '';
+
+ if (qm.hasArguments())
+ {
+ template += '\n---' // separator
+ + '\nACTUAL ARGS: {6}' // actual args
+ + '\n---' // separator
+ + '\nEXPECTED ARGS: {7}'; // matcher descriptions
+
+ if (expectedMatchers == null)
+ {
+ expectedDescription = describe(expectedArguments);
+ }
+ else
+ {
+ expectedDescription = describe(expectedMatchers);
+ }
+ actualDescription = describe(actualArguments);
+ }
+
+ String message = String.format(template, new List{
+ '' + expectedCount,
+ String.isBlank(qualifier) ? '' : ('' + qualifier),
+ inOrder,
+ '' + methodCount,
+ '' + qm,
+ String.isBlank(customAssertMessage) ? '' : ('\n' + customAssertMessage),
+ actualDescription,
+ expectedDescription
+ });
+
+ throw new fflib_ApexMocks.ApexMocksException(message);
+ }
+
+ private static String describe(List matchers)
+ {
+ List descriptions = new List();
+ for (fflib_IMatcher matcher : matchers)
+ {
+ descriptions.add('' + matcher);
+ }
+
+ return String.join(descriptions, ', ');
+ }
+
+ private static String describe(List valuesFromAllInvocations)
+ {
+ List descriptions = new List();
+ if (valuesFromAllInvocations != null)
+ {
+ for (fflib_MethodArgValues valuesFromOneInvocation : valuesFromAllInvocations)
+ {
+ descriptions.add(describe(valuesFromOneInvocation));
+ }
+ }
+
+ return '(' + String.join(descriptions, '), (') + ')';
+ }
+
+ private static String describe(fflib_MethodArgValues values)
+ {
+ List descriptions = new List();
+ for (Object value : values.argValues)
+ {
+ try
+ {
+ // Attempt to JSON serialize - that way it doesn't truncate SObject fields etc.
+ // Bear in mind that something are not JSON serializable, e.g. things with circular references.
+ descriptions.add(JSON.serialize(value));
+ }
+ catch (Exception error)
+ {
+ descriptions.add('' + value);
+ }
+ }
+
+ return String.join(descriptions, ', ');
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_MethodVerifier.cls-meta.xml b/sfdx-source/apex-mocks/fflib_MethodVerifier.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_MethodVerifier.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_QualifiedMethod.cls b/sfdx-source/apex-mocks/fflib_QualifiedMethod.cls
new file mode 100644
index 00000000000..521106992d0
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_QualifiedMethod.cls
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2016-2017 FinancialForce.com, inc. All rights reserved.
+ */
+public with sharing class fflib_QualifiedMethod
+{
+ public final Object mockInstance;
+ public final String typeName;
+ public final String methodName;
+ public final List methodArgTypes;
+
+ public fflib_QualifiedMethod(String typeName, String methodName, List methodArgTypes)
+ {
+ this(typeName, methodName, methodArgTypes, null);
+ }
+
+ public fflib_QualifiedMethod(String typeName, String methodName, List methodArgTypes, Object mockInstance)
+ {
+ this.mockInstance = mockInstance;
+ this.typeName = typeName;
+ this.methodName = methodName;
+ this.methodArgTypes = methodArgTypes;
+ }
+
+ /**
+ * Standard equals override.
+ * @param other The object whose equality we are verifying
+ * @return Boolean True if meaningfully equivalent, false otherwise.
+ */
+ public Boolean equals(Object other)
+ {
+ if (this === other)
+ {
+ return true;
+ }
+
+ fflib_QualifiedMethod that = other instanceof fflib_QualifiedMethod ? (fflib_QualifiedMethod)other : null;
+
+ return that != null
+ && (this.mockInstance === that.mockInstance || !fflib_ApexMocksConfig.HasIndependentMocks)
+ && this.typeName == that.typeName
+ && this.methodName == that.methodName
+ && this.methodArgTypes == that.methodArgTypes;
+ }
+
+ /**
+ * Standard hashCode override.
+ * @return Integer The generated hashCode
+ */
+ public Integer hashCode()
+ {
+ Integer prime = 31;
+ Integer result = 1;
+
+ if (fflib_ApexMocksConfig.HasIndependentMocks)
+ {
+ result = prime * result + ((mockInstance == null) ? 0 : mockInstance.hashCode());
+ }
+ result = prime * result + ((methodArgTypes == null) ? 0 : methodArgTypes.hashCode());
+ result = prime * result + ((methodName == null) ? 0 : methodName.hashCode());
+ result = prime * result + ((typeName == null) ? 0 : typeName.hashCode());
+
+ return result;
+ }
+
+ /**
+ * Standard toString override.
+ * @return String The human friendly description of the method.
+ */
+ public override String toString()
+ {
+ return typeName + '.' + methodName + methodArgTypes;
+ }
+
+ /**
+ * Predicate describing whether the qualified method accepts arguments or not.
+ * @return True if the method accepts arguments.
+ */
+ public Boolean hasArguments()
+ {
+ return this.methodArgTypes != null && !this.methodArgTypes.isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_QualifiedMethod.cls-meta.xml b/sfdx-source/apex-mocks/fflib_QualifiedMethod.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_QualifiedMethod.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/fflib_QualifiedMethodAndArgValues.cls b/sfdx-source/apex-mocks/fflib_QualifiedMethodAndArgValues.cls
new file mode 100644
index 00000000000..fb25c2e4c3a
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_QualifiedMethodAndArgValues.cls
@@ -0,0 +1,40 @@
+/*
+ Copyright (c) 2016-2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * @group Core
+ */
+public with sharing class fflib_QualifiedMethodAndArgValues
+{
+ private final fflib_QualifiedMethod qm;
+ private final fflib_MethodArgValues args;
+ private final Object mockInstance;
+
+ public fflib_QualifiedMethodAndArgValues(fflib_QualifiedMethod qm, fflib_MethodArgValues args, Object mockInstance)
+ {
+ this.qm = qm;
+ this.args = args;
+ this.mockInstance = mockInstance;
+ }
+
+ public fflib_QualifiedMethod getQualifiedMethod()
+ {
+ return qm;
+ }
+
+ public fflib_MethodArgValues getMethodArgValues()
+ {
+ return args;
+ }
+
+ public Object getMockInstance()
+ {
+ return mockInstance;
+ }
+
+ public override String toString()
+ {
+ return qm + ' with args: [' + String.join(args.argValues, '],[') + ']';
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_QualifiedMethodAndArgValues.cls-meta.xml b/sfdx-source/apex-mocks/fflib_QualifiedMethodAndArgValues.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_QualifiedMethodAndArgValues.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_System.cls b/sfdx-source/apex-mocks/fflib_System.cls
new file mode 100644
index 00000000000..476b81f44c8
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_System.cls
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * @group Core
+ * Contains counterparts for helper methods in the native System class.
+ */
+
+public class fflib_System
+{
+ /**
+ * Verifies that the supplied argument is meaningfully equivalent to the expected argument, as defined by its matcher.
+ * See fflib_SystemTest for examples of usage.
+ * @param ignoredRetval Dummy value, returned on registering an fflib_IMatcher.
+ * @param value The object instance upon which we are checking equality.
+ */
+ public static void assertEquals(Object ignoredRetval, Object value)
+ {
+ assertEquals(ignoredRetval, value, null);
+ }
+
+ /**
+ * Verifies that the supplied argument is meaningfully equivalent to the expected argument, as defined by its matcher.
+ * See fflib_SystemTest for examples of usage.
+ * @param ignoredRetval Dummy value, returned on registering an fflib_IMatcher.
+ * @param value The object instance upon which we are checking equality.
+ * @param customAssertMessage Provides context or additional information for the assertion.
+ */
+ public static void assertEquals(Object ignoredRetval, Object value, String customAssertMessage)
+ {
+ fflib_IMatcher matcher = null;
+ try
+ {
+ List matchers = fflib_Match.getAndClearMatchers(1);
+ matcher = matchers[0];
+ }
+ catch (fflib_ApexMocks.ApexMocksException e)
+ {
+ throw new fflib_ApexMocks.ApexMocksException('fflib_System.assertEquals expects you to register exactly 1 fflib_IMatcher (typically through the helpers in fflib_Match).');
+ }
+
+ if (!matcher.matches(value))
+ {
+ throw new fflib_ApexMocks.ApexMocksException(String.format('Expected : {0}, Actual: {1}{2}', new String[]{
+ String.valueOf(matcher),
+ String.valueOf(value),
+ String.isBlank(customAssertMessage) ? '' : (' -- ' + customAssertMessage)
+ }));
+ }
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_System.cls-meta.xml b/sfdx-source/apex-mocks/fflib_System.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_System.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/fflib_VerificationMode.cls b/sfdx-source/apex-mocks/fflib_VerificationMode.cls
new file mode 100644
index 00000000000..1bf014b381b
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_VerificationMode.cls
@@ -0,0 +1,139 @@
+/*
+ Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * This class implements the verification modes with Mockito syntax style.
+ * It can be used in the classic verify and in the ordered verify.
+ * @group Core
+ */
+public with sharing class fflib_VerificationMode
+{
+ public Integer VerifyMin {get; set;}
+ public Integer VerifyMax {get; set;}
+ public String CustomAssertMessage { get; set; }
+
+ public enum ModeName {times, atLeast, atMost, between, atLeastOnce, calls}
+
+ public ModeName Method;
+
+ public fflib_VerificationMode()
+ {
+ VerifyMin = 1;
+ VerifyMax = null;
+ CustomAssertMessage = null;
+ Method = null;
+ }
+
+ /**
+ * Sets how many times the method is expected to be called.
+ * For InOrder verification we copy Mockito behavior which is as follows;
+ *
+ * Consume the specified number of matching invocations, ignoring non-matching invocations in between
+ * Fail an assert if the very next invocation matches, but additional matches can still exist so long as at least one non-matching invocation exists before them
+ *
+ * For example if you had a(); a(); b(); a();
+ * then inOrder.verify(myMock, 2)).a(); or inOrder.verify(myMock, 3)).a(); would pass but not inOrder.verify(myMock, 1)).a();
+ * @param times The number of times you expect the method to have been called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode times(Integer times)
+ {
+ this.Method = ModeName.times;
+ this.VerifyMin = this.VerifyMax = times;
+ return this;
+ }
+
+ /**
+ * Sets a custom assert message for the verify.
+ * @param customAssertMessage The custom message for the assert in case the assert is false. The custom message is queued to the default message.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode description(String customAssertMessage)
+ {
+ this.CustomAssertMessage = customAssertMessage;
+ return this;
+ }
+
+ /**
+ * Sets the minimum number of times the method is expected to be called.
+ * With the InOrder verification it performs a greedy verification, which means it would consume all the instances of the method verified.
+ * @param atLeastTimes The minimum number of times you expect the method to have been called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode atLeast(Integer atLeastTimes)
+ {
+ this.Method = ModeName.atLeast;
+ this.VerifyMin = atLeastTimes;
+
+ return this;
+ }
+
+ /**
+ * Sets the maximum number of times the method is expected to be called. Not available in the InOrder verification.
+ * @param atMostTimes The maximum number of times the method is expected to be called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode atMost(Integer atMostTimes)
+ {
+ this.Method = ModeName.atMost;
+ this.VerifyMax = atMostTimes;
+
+ return this;
+ }
+
+ /**
+ * Sets that the method is called at least once.
+ * With the InOrder verification it performs a greedy verification, which means it would consume all the instances of the method verified.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode atLeastOnce()
+ {
+ this.Method = ModeName.atLeastOnce;
+ this.VerifyMin = 1;
+
+ return this;
+ }
+
+ /**
+ * Sets the range of how many times the method is expected to be called. Not available in the InOrder verification.
+ * @param atLeastTimes The minimum number of times you expect the method to have been called.
+ * @param atMostTimes The maximum number of times the method is expected to be called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode between(Integer atLeastTimes, Integer atMostTimes)
+ {
+ this.Method = ModeName.between;
+ this.VerifyMin = atLeastTimes;
+ this.VerifyMax = atMostTimes;
+
+ return this;
+ }
+
+ /**
+ * Sets that the method is not expected to be called.
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode never()
+ {
+ this.VerifyMin = fflib_ApexMocks.NEVER;
+ this.VerifyMax = fflib_ApexMocks.NEVER;
+
+ return this;
+ }
+
+ /**
+ * Sets how many times the method is expected to be called for an InOrder verifier. Available Only with the InOrder verification.
+ * A verification mode using calls will not fail if the method is called more times than expected.
+ * @param callingTimes The number of times you expect the method to have been called in the InOrder verifying (no greedy verify).
+ * @return The fflib_VerificationMode object instance with the proper settings.
+ */
+ public fflib_VerificationMode calls(Integer callingTimes)
+ {
+ this.Method = ModeName.calls;
+ this.VerifyMin = callingTimes;
+ this.VerifyMax = null;
+
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/fflib_VerificationMode.cls-meta.xml b/sfdx-source/apex-mocks/fflib_VerificationMode.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/fflib_VerificationMode.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/tests/fflib_AnswerTest.cls b/sfdx-source/apex-mocks/tests/fflib_AnswerTest.cls
new file mode 100644
index 00000000000..63463099e04
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_AnswerTest.cls
@@ -0,0 +1,438 @@
+/*
+ Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * @nodoc
+ */
+@isTest
+private class fflib_AnswerTest
+{
+
+ private static fflib_InvocationOnMock actualInvocation = null;
+
+ @isTest
+ static void thatAnswersWithException()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.ExceptionForAnswer());
+ mocks.stopStubbing();
+
+ // When
+ try
+ {
+ mockList.get2(0, 'Hi hi Hello Hi hi');
+ System.assert(false, 'an exception is expected to be thrown on the answer execution');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ansExpt)
+ {
+ String expectedMessage = 'an error occurs on the execution of the answer';
+ // Then
+ System.assertEquals(expectedMessage, ansExpt.getMessage(), 'the message from the answer is not as expected');
+ }
+ }
+
+ @isTest
+ static void thatStoresMethodIntoInvocationOnMock()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.BasicAnswer());
+ mocks.stopStubbing();
+
+ // When
+ mockList.get2(0, 'Hi hi Hello Hi hi');
+
+ // Then
+
+ Object methodCalled = actualInvocation.getMethod();
+ System.assert(methodCalled instanceof fflib_QualifiedMethod, 'the object returned is not a method as expected');
+
+ String expectedMethodSignature = fflib_MyList.getStubClassName() + '.get2(Integer, String)';
+ System.assertEquals(expectedMethodSignature, ((fflib_QualifiedMethod)methodCalled).toString(), ' the method is no the one expected');
+ }
+
+ @isTest
+ static void thatAnswerOnlyForTheMethodStubbedWithAnswer()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get(3)).thenReturn('ted');
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.BasicAnswer());
+ mocks.stopStubbing();
+
+ // When
+ mockList.add('one');
+ String noAnswered = mockList.get(3);
+ mockList.get2(0, 'Hi hi Hello Hi hi');
+
+ // Then
+ Object methodCalled = actualInvocation.getMethod();
+ System.assert(methodCalled instanceof fflib_QualifiedMethod, 'the object returned is not a method as expected');
+
+ String expectedMethodSignature = fflib_MyList.getStubClassName() + '.get2(Integer, String)';
+ System.assertEquals(expectedMethodSignature, ((fflib_QualifiedMethod)methodCalled).toString(), ' the method is no the one expected');
+
+ System.assertEquals('ted', noAnswered, 'the get method should have returned the stubbed string');
+ }
+
+ @isTest
+ static void thatMultipleAnswersAreHandled()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get(3)).thenAnswer(new fflib_AnswerTest.FirstAnswer());
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.SecondAnswer());
+ mocks.stopStubbing();
+
+ // When
+ mockList.add('one');
+ String answer1 = mockList.get(3);
+ String answer2 = mockList.get2(0, 'Hi hi Hello Hi hi');
+
+ System.assertEquals('this is the first answer', answer1, 'the answer wasnt the one expected');
+ System.assertEquals('and this is the second one', answer2, 'the answer wasnt the one expected');
+ }
+
+ @isTest
+ static void thatStoresMockInstanceIntoInvocationOnMock()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.BasicAnswer());
+ mocks.stopStubbing();
+
+ // When
+ String mockCalled = mockList.get2(0, 'Hi hi Hello Hi hi');
+
+ // Then
+ System.assert(actualInvocation.getMock() instanceof fflib_MyList.IList, 'the object returned is not a mock instance as expected');
+ System.assertEquals(mockList, actualInvocation.getMock(), 'the mock returned should be the mockList used in the stubbing');
+ }
+
+ @isTest
+ static void thatMethodsParametersAreAccessible()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.ProcessArgumentAnswer());
+ mocks.stopStubbing();
+
+ // When
+ String actualValue = mockList.get2(0, 'Hi hi Hello Hi hi');
+
+ // Then
+ System.assertEquals('Bye hi Hello Bye hi', actualValue, 'the answer is not correct');
+ }
+
+ @isTest
+ static void thatAnswerOnlyForTheStubbedParameter()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.ProcessArgumentAnswer());
+ mocks.stopStubbing();
+
+ // When
+ String actualValue1 = mockList.get2(0, 'some string for my method');
+ String actualValue2 = mockList.get2(0, 'Hi hi Hello Hi hi');
+ String actualValue3 = mockList.get2(0, 'another string for the same method');
+
+ // Then
+ System.assertEquals('Bye hi Hello Bye hi', actualValue2, 'the answer is not correct');
+ System.assertEquals(null, actualValue1, 'the answer is not correct');
+ System.assertEquals(null, actualValue3, 'the answer is not correct');
+ }
+
+ @isTest
+ static void thatMethodsParametersAreAccessibleWhenCalledWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(fflib_Match.anyInteger(), fflib_Match.anyString())).thenAnswer(new fflib_AnswerTest.ProcessArgumentAnswer());
+ mocks.stopStubbing();
+
+ // When
+ String actualValue = mockList.get2(0, 'Hi hi Hello Hi hi');
+
+ // Then
+ System.assertEquals('Bye hi Hello Bye hi', actualValue, 'the answer is not correct');
+ }
+
+ @isTest
+ static void thatExceptionIsThrownWhenAccessOutOfIndexArgument()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.ExceptionForArgumentsOutOfBound());
+ mocks.stopStubbing();
+
+ // When
+ String actualValue = mockList.get2(0, 'Hi hi Hello Hi hi');
+ }
+
+ @isTest
+ static void thatExceptionIsThrownWhenAccessNegativeIndexArgument()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(0, 'Hi hi Hello Hi hi')).thenAnswer(new fflib_AnswerTest.ExceptionForNegativeArgumentIndex());
+ mocks.stopStubbing();
+
+ // When
+ String actualValue = mockList.get2(0, 'Hi hi Hello Hi hi');
+ }
+
+ @isTest
+ static void thatArgumentListEmptyForMethodWithNoArgument()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.isEmpty()).thenAnswer(new fflib_AnswerTest.ArgumentListEmptyForMethodWithNoArgument());
+ mocks.stopStubbing();
+
+ // When
+ Boolean actualValue = mockList.isEmpty();
+ }
+
+ @isTest
+ static void thatAnswerToVoidMethod()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ ((fflib_MyList) mocks.doAnswer(new fflib_AnswerTest.BasicAnswer(), mockList)).addMore('Hi hi Hello Hi hi');
+ mocks.stopStubbing();
+
+ // When
+ mockList.addMore('Hi hi Hello Hi hi');
+
+ // Then
+ Object methodCalled = actualInvocation.getMethod();
+ System.assert(methodCalled instanceof fflib_QualifiedMethod, 'the object returned is not a method as expected');
+
+ String expectedMethodSignature = fflib_MyList.getStubClassName() + '.addMore(String)';
+ System.assertEquals(expectedMethodSignature, ((fflib_QualifiedMethod)methodCalled).toString(), 'Unexpected method name: ' + methodCalled);
+ }
+
+ @isTest
+ static void thatAnswerToVoidAndNotVoidMethods()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ ((fflib_MyList) mocks.doAnswer(new fflib_AnswerTest.FirstAnswer(), mockList)).get(3);
+ ((fflib_MyList) mocks.doAnswer(new fflib_AnswerTest.BasicAnswer(), mockList)).addMore('Hi hi Hello Hi hi');
+ ((fflib_MyList) mocks.doAnswer(new fflib_AnswerTest.SecondAnswer(), mockList)).get2(4, 'Hi hi Hello Hi hi');
+ mocks.stopStubbing();
+
+ // When
+ String answer1 = mockList.get(3);
+ String answer2 = mockList.get2(4, 'Hi hi Hello Hi hi');
+ mockList.addMore('Hi hi Hello Hi hi');
+
+ // Then
+ Object methodCalled = actualInvocation.getMethod();
+ System.assert(methodCalled instanceof fflib_QualifiedMethod, 'the object returned is not a method as expected');
+
+ String expectedMethodSignature = fflib_MyList.getStubClassName() + '.addMore(String)';
+ System.assertEquals(expectedMethodSignature, ((fflib_QualifiedMethod)methodCalled).toString(),
+ 'the last method called should be the addMore, so should be the last to set the actualInvocation variable.');
+
+ System.assertEquals('this is the first answer', answer1, 'the answer was not the one expected');
+ System.assertEquals('and this is the second one', answer2, 'the answer was not the one expected');
+ }
+
+ @isTest
+ static void thatAnswerToDifferentVoidMethods()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ fflib_AnswerTest.FirstAnswer answer1 = new fflib_AnswerTest.FirstAnswer();
+ fflib_AnswerTest.SecondAnswer answer2 = new fflib_AnswerTest.SecondAnswer();
+
+ System.assertEquals(null, answer1.getMessage(), 'the answer message should be null at this stage');
+ System.assertEquals(null, answer2.getMessage(), 'the answer message should be null at this stage');
+
+ mocks.startStubbing();
+ ((fflib_MyList) mocks.doAnswer(answer1, mockList)).addMore('Hi hi Hello Hi hi');
+ ((fflib_MyList) mocks.doAnswer(answer2, mockList)).add('Hello');
+ mocks.stopStubbing();
+
+ // When
+ mockList.addMore('Hi hi Hello Hi hi');
+ mockList.add('Hello');
+
+ // Then
+ System.assertEquals('this is the first answer', answer1.getMessage(), 'the answer was not the one expected');
+ System.assertEquals('and this is the second one', answer2.getMessage(), 'the answer was not the one expected');
+ }
+
+ //Answers
+
+ public class BasicAnswer implements fflib_Answer
+ {
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ actualInvocation = invocation;
+ return null;
+ }
+ }
+
+ public class ExceptionForAnswer implements fflib_Answer
+ {
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ actualInvocation = invocation;
+
+ throw new fflib_ApexMocks.ApexMocksException('an error occurs on the execution of the answer');
+ }
+ }
+
+ public class ExceptionForArgumentsOutOfBound implements fflib_Answer
+ {
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ actualInvocation = invocation;
+
+ try
+ {
+ Object noExistingObj = invocation.getArgument(2);
+ System.assert(false, 'an exception was expected because the argument in the method are only 2');
+ }
+ catch(fflib_ApexMocks.ApexMocksException exp)
+ {
+ String expectedMessage = 'Invalid index, must be greater or equal to zero and less of 2.';
+ String actualMessage = exp.getMessage();
+ System.assertEquals(expectedMessage, actualMessage, 'the message return by the exception is not as expected');
+ }
+ return null;
+ }
+ }
+
+ public class ExceptionForNegativeArgumentIndex implements fflib_Answer
+ {
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ actualInvocation = invocation;
+
+ try
+ {
+ Object noExistingObj = invocation.getArgument(-1);
+ System.assert(false, 'an exception was expected because the argument index cannot be negative');
+ }
+ catch(fflib_ApexMocks.ApexMocksException exp)
+ {
+ String expectedMessage = 'Invalid index, must be greater or equal to zero and less of 2.';
+ String actualMessage = exp.getMessage();
+ System.assertEquals(expectedMessage, actualMessage, 'the message return by the exception is not as expected');
+ }
+ return null;
+ }
+ }
+
+ public class ArgumentListEmptyForMethodWithNoArgument implements fflib_Answer
+ {
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ actualInvocation = invocation;
+
+ List emptyList = invocation.getArguments();
+
+ System.assertEquals(0, emptyList.size(), 'the argument list from a method without arguments should be empty');
+
+ return null;
+ }
+ }
+
+ public class FirstAnswer implements fflib_Answer
+ {
+ private String answerMessage;
+
+ public String getMessage()
+ {
+ return this.answerMessage;
+ }
+
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ actualInvocation = invocation;
+
+ this.answerMessage = 'this is the first answer';
+
+ return answerMessage;
+ }
+ }
+
+ public class SecondAnswer implements fflib_Answer
+ {
+ private String answerMessage;
+
+ public String getMessage()
+ {
+ return this.answerMessage;
+ }
+
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ actualInvocation = invocation;
+
+ this.answerMessage = 'and this is the second one';
+
+ return answerMessage;
+ }
+ }
+
+ public class ProcessArgumentAnswer implements fflib_Answer
+ {
+ public Object answer(fflib_InvocationOnMock invocation)
+ {
+ actualInvocation = invocation;
+
+ String argument = (String) invocation.getArgument(1);
+ System.assertNotEquals(null, argument, ' the argument should have some value');
+ argument = argument.replace('Hi', 'Bye');
+ return argument;
+ }
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/tests/fflib_AnswerTest.cls-meta.xml b/sfdx-source/apex-mocks/tests/fflib_AnswerTest.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_AnswerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/tests/fflib_AnyOrderTest.cls b/sfdx-source/apex-mocks/tests/fflib_AnyOrderTest.cls
new file mode 100644
index 00000000000..5651b31f48f
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_AnyOrderTest.cls
@@ -0,0 +1,1195 @@
+/*
+ Copyright (c) 2017 FinancialForce.com, inc. All rights reserved.
+ */
+
+/**
+ * @nodoc
+ */
+@isTest
+private class fflib_AnyOrderTest
+{
+ /*
+ * replicating the apex mocks tests with the new syntax
+ */
+
+ @isTest
+ private static void whenVerifyMultipleCallsWithMatchersShouldReturnCorrectMethodCallCounts()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).add(fflib_Match.anyString());
+ ((fflib_MyList.IList) mocks.verify(mockList)).add('fred');
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(fflib_Match.stringContains('fred'));
+ }
+
+ @isTest
+ private static void whenVerifyWithCombinedMatchersShouldReturnCorrectMethodCallCounts()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).add(
+ (String)fflib_Match.allOf(fflib_Match.eq('bob'), fflib_Match.stringContains('re'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(
+ (String)fflib_Match.allOf(fflib_Match.eq('fred'), fflib_Match.stringContains('re'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).add(
+ (String)fflib_Match.anyOf(fflib_Match.eq('bob'), fflib_Match.eq('fred'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(
+ (String)fflib_Match.anyOf(fflib_Match.eq('bob'), fflib_Match.eq('jack'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).add(
+ (String)fflib_Match.noneOf(fflib_Match.eq('jack'), fflib_Match.eq('tim'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).add(
+ (String)fflib_Match.noneOf(
+ fflib_Match.anyOf(fflib_Match.eq('jack'), fflib_Match.eq('jill')),
+ fflib_Match.allOf(fflib_Match.eq('tim'), fflib_Match.stringContains('i'))
+ )
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).add(
+ (String)fflib_Match.isNot(fflib_Match.eq('jack'))
+ );
+ }
+
+ @isTest
+ private static void whenVerifyCustomMatchersCanBeUsed()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.get(1);
+ mockList.get(2);
+ mockList.get(3);
+ mockList.get(4);
+ mockList.get(5);
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(3))).get((Integer)fflib_Match.matches(new isOdd()));
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).get((Integer)fflib_Match.matches(new isEven()));
+ }
+
+ @isTest
+ private static void verifyMultipleMethodCallsWithSameSingleArgument()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).add('bob');
+ }
+
+ @isTest
+ private static void verifyMethodNotCalled()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.get(0);
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).add('bob');
+ ((fflib_MyList.IList) mocks.verify(mockList)).get(0);
+ }
+
+ @isTest
+ private static void verifySingleMethodCallWithMultipleArguments()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.set(0, 'bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList)).set(0, 'bob');
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).set(0, 'fred');
+ }
+
+ @isTest
+ private static void verifyMethodCallWhenNoCallsBeenMadeForType()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).add('bob');
+ }
+
+ @isTest
+ private static void whenVerifyMethodNeverCalledMatchersAreReset()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).get(fflib_Match.anyInteger());
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(fflib_Match.anyString());
+ }
+
+ /*
+ * times
+ */
+
+ @isTest
+ private static void verifyTimesMethodHasBeenCalled()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(3))).add('bob');
+ }
+
+ @isTest
+ private static void verifyTimesMethodHasBeenCalledWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob1');
+ mockList.add('bob2');
+ mockList.add('bob3');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(3))).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatVerifyTimesMethodFailsWhenCalledLessTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(4))).add('bob');
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(Exception exc)
+ {
+ assertFailMessage(exc.getMessage(), 4, 3);
+ }
+ }
+
+ @isTest
+ private static void thatVerifyTimesMethodFailsWhenCalledMoreTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).add('bob');
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(Exception exc)
+ {
+ assertFailMessage(exc.getMessage(), 2, 3);
+ }
+ }
+
+ @isTest
+ private static void thatVerifyTimesMethodFailsWhenCalledLessTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(4))).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(Exception exc)
+ {
+ assertFailMessage(exc.getMessage(), 4, 3);
+ }
+ }
+
+ @isTest
+ private static void thatVerifyTimesMethodFailsWhenCalledMoreTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2))).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(Exception exc)
+ {
+ assertFailMessage(exc.getMessage(), 2, 3);
+ }
+ }
+
+ /*
+ * description
+ */
+
+ @isTest
+ private static void thatCustomMessageIsAdded()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ String customAssertMessage = 'Custom message to explain the reason of the verification';
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.times(2).description(customAssertMessage))).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(Exception exc)
+ {
+ System.assertEquals('EXPECTED COUNT: 2'
+ + '\nACTUAL COUNT: 3'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\nCustom message to explain the reason of the verification'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("bob"), ("bob")'
+ + '\n---'
+ + '\nEXPECTED ARGS: [any String]',
+ exc.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ /*
+ * atLeast
+ */
+
+ @isTest
+ private static void thatVerifiesAtLeastNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeast(2))).add('bob');
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastNumberOfTimesWhenIsCalledMoreTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeast(2))).add('bob');
+ }
+
+ @isTest
+ private static void thatThrownExceptionIfCalledLessThanAtLeastNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeast(3))).add('bob');
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called 3 times when instead is called only twice');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 3 or more times'
+ + '\nACTUAL COUNT: 2'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred"), ("bob")'
+ + '\n---'
+ + '\nEXPECTED ARGS: "bob"',
+ ex.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeast(2))).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastNumberOfTimesWhenIsCalledMoreTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeast(2))).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatThrownExceptionIfCalledLessThanAtLeastNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeast(3))).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called 3 times when instead is called only twice');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 3 or more times'
+ + '\nACTUAL COUNT: 2'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred")'
+ + '\n---'
+ + '\nEXPECTED ARGS: [any String]',
+ ex.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ /*
+ * atMost
+ */
+
+ @isTest
+ private static void thatVerifiesAtMostNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atMost(5))).add('bob');
+ }
+
+ @isTest
+ private static void thatVerifiesAtMostSameNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atMost(3))).add('bob');
+ }
+
+ @isTest
+ private static void thatThrownExceptionIfCalledMoreThanAtMostNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atMost(3))).add('bob');
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called 3 times when instead is called four times');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 3 or fewer times'
+ + '\nACTUAL COUNT: 4'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred"), ("fred"), ("bob"), ("bob"), ("bob"), ("fred")'
+ + '\n---'
+ + '\nEXPECTED ARGS: "bob"',
+ ex.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ @isTest
+ private static void thatVerifiesAtMostNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atMost(5))).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatVerifiesAtMostSameNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atMost(3))).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatThrownExceptionIfCalledMoreThanAtMostNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+ mockList.add('fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atMost(3))).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called 3 times when instead is called four times');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 3 or fewer times'
+ + '\nACTUAL COUNT: 4'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred"), ("fred"), ("fred")'
+ + '\n---'
+ + '\nEXPECTED ARGS: [any String]'
+ , ex.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ /*
+ * atLeastOnce
+ */
+
+ @isTest
+ private static void thatVerifiesAtLeastOnceNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add('bob');
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastOnceNumberOfTimesWhenIsCalledMoreTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add('bob');
+ }
+
+ @isTest
+ private static void thatThrownExceptionIfCalledLessThanAtLeastOnceNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('rob');
+ mockList.add('fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add('bob');
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called at least once when instead is never called');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 1 or more times'
+ + '\nACTUAL COUNT: 0'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("rob"), ("fred")'
+ + '\n---'
+ + '\nEXPECTED ARGS: "bob"',
+ ex.getmessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastOnceNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred', 'fred', 'fred', 'fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastOnceNumberOfTimesWhenIsCalledMoreTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred', 'fred', 'fred', 'fred');
+ mockList.add('bob');
+ mockList.add('fred', 'fred', 'fred', 'fred');
+ mockList.add('bob');
+ mockList.add('fred', 'fred', 'fred', 'fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatThrownExceptionIfCalledLessThanAtLeastOnceNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('fred', 'fred', 'fred', 'fred');
+ mockList.add('fred', 'fred', 'fred', 'fred');
+ mockList.add('fred', 'fred', 'fred', 'fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called at lest once when instead is never called');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 1 or more times'
+ + '\nACTUAL COUNT: 0'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ()'
+ + '\n---'
+ + '\nEXPECTED ARGS: [any String]',
+ ex.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ /*
+ * between
+ */
+
+ @isTest
+ private static void thatVerifiesBetweenNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.between(3, 5))).add('bob');
+ }
+
+ @isTest
+ private static void thatBetweenThrownExceptionIfCalledLessThanAtLeastNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.between(3, 5))).add('bob');
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called at least 3 times when instead is called only twice');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 3 or more times'
+ + '\nACTUAL COUNT: 2'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred"), ("bob")'
+ + '\n---'
+ + '\nEXPECTED ARGS: "bob"',
+ ex.getMessage()
+ );
+ }
+ }
+
+ @isTest
+ private static void thatVerifiesBetweenNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.between(3, 5))).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatBetweenThrownExceptionIfCalledLessThanAtLeastNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.between(3, 5))).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called 3 times when instead is called only twice');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 3 or more times'
+ + '\nACTUAL COUNT: 2'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred")'
+ + '\n---'
+ + '\nEXPECTED ARGS: [any String]',
+ ex.getMessage()
+ );
+ }
+ }
+
+ @isTest
+ private static void thatBetweenThrownExceptionIfCalledMoreThanAtMostNumberOfTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.between(3, 5))).add('bob');
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called at most 5 times when instead is called six times');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 5 or fewer times'
+ + '\nACTUAL COUNT: 6'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred"), ("fred"), ("bob"), ("bob"), ("bob"), ("bob"), ("bob"), ("fred")'
+ + '\n---'
+ + '\nEXPECTED ARGS: "bob"',
+ ex.getMessage()
+ );
+ }
+ }
+
+ @isTest
+ private static void thatBetweenThrownExceptionIfCalledMoreThanAtMostNumberOfTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+ mockList.add('fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.between(3, 5))).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called 5 times when instead is called six times');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 5 or fewer times'
+ + '\nACTUAL COUNT: 6'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred"), ("bob"), ("fred"), ("fred"), ("fred")'
+ + '\n---'
+ + '\nEXPECTED ARGS: [any String]',
+ ex.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ /*
+ * never
+ */
+
+ @isTest
+ private static void verifyNeverMethodHasNotBeenCalled()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob1');
+ mockList.add('bob2');
+ mockList.add('bob3');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).add('bob');
+ }
+
+ @isTest
+ private static void verifyNeverMethodHasBeenNotCalledWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('fred', 'fred', 'fred', 'fred');
+ mockList.add('fred', 'fred', 'fred', 'fred');
+ mockList.add('fred', 'fred', 'fred', 'fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatVerifyNeverFailsWhenCalledMoreTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).add('bob');
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(Exception exc)
+ {
+ assertFailMessage(exc.getMessage(), 0, 2);
+ }
+ }
+
+ @isTest
+ private static void thatVerifyNeverFailsWhenCalledMoreTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.never())).add(fflib_Match.anyString());
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(Exception exc)
+ {
+ assertFailMessage(exc.getMessage(), 0, 3);
+ }
+ }
+
+
+
+ /*
+ * atLeastOnce
+ */
+
+ @isTest
+ private static void thatVerifiesAtLeastOnce()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add('bob');
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastOnceWhenIsCalledMoreTimes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add('bob');
+ }
+
+ @isTest
+ private static void thatThrownExceptionIfCalledLessThanAtLeastOnce()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('bob');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add('rob');
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called one times when instead is not called');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 1 or more times'
+ + '\nACTUAL COUNT: 0'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred"), ("bob")'
+ + '\n---'
+ + '\nEXPECTED ARGS: "rob"',
+ ex.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastOnceWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatVerifiesAtLeastOnceWhenIsCalledMoreTimesWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+ mockList.add('fred');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ private static void thatThrownExceptionIfCalledLessThanAtLeastOnceWithMatchers()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList, mocks.atLeastOnce())).add(fflib_Match.stringStartsWith('rob'));
+
+ System.assert(false, 'an exception was expected because we are asserting that the method is called once when instead is not called');
+ }
+ catch(fflib_ApexMocks.ApexMocksException ex)
+ {
+ System.assertEquals('EXPECTED COUNT: 1 or more times'
+ + '\nACTUAL COUNT: 0'
+ + '\nMETHOD: fflib_MyList__sfdc_ApexStub.add(String)'
+ + '\n---'
+ + '\nACTUAL ARGS: ("bob"), ("fred")'
+ + '\n---'
+ + '\nEXPECTED ARGS: [starts with "rob"]',
+ ex.getMessage(),
+ 'Unexpected verify fail message');
+ }
+ }
+
+ /*
+ * HELPER METHODS
+ */
+
+ private static void assertFailMessage(String exceptionMessage, Integer expectedInvocations, Integer actualsInvocations)
+ {
+ System.assert(
+ exceptionMessage.startsWith('EXPECTED COUNT: ' + expectedInvocations + '\nACTUAL COUNT: ' + actualsInvocations),
+ 'Unexpected verify fail message: ' + exceptionMessage
+ );
+ }
+
+ /*
+ * HELPER CLASSES
+ */
+
+ private class isOdd implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg instanceof Integer ? Math.mod((Integer)arg, 2) == 1: false;
+ }
+ }
+
+ private class isEven implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg instanceof Integer ? Math.mod((Integer)arg, 2) == 0: false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/tests/fflib_AnyOrderTest.cls-meta.xml b/sfdx-source/apex-mocks/tests/fflib_AnyOrderTest.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_AnyOrderTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/tests/fflib_ApexMocksTest.cls b/sfdx-source/apex-mocks/tests/fflib_ApexMocksTest.cls
new file mode 100644
index 00000000000..c90b847fe52
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_ApexMocksTest.cls
@@ -0,0 +1,1627 @@
+/*
+ * Copyright (c) 2014-2017 FinancialForce.com, inc. All rights reserved.
+ */
+@isTest
+private class fflib_ApexMocksTest
+{
+ private static final fflib_ApexMocks MY_MOCKS = new fflib_ApexMocks();
+ private static final fflib_MyList MY_MOCK_LIST = (fflib_MyList)MY_MOCKS.mock(fflib_MyList.class);
+
+ @isTest
+ static void whenStubMultipleCallsWithMatchersShouldReturnExpectedValues()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(fflib_Match.anyInteger(), fflib_Match.anyString())).thenReturn('any');
+ mocks.when(mockList.get2(fflib_Match.anyInteger(), fflib_Match.stringContains('Hello'))).thenReturn('hello');
+ mocks.stopStubbing();
+
+ // When
+ String actualValue = mockList.get2(0, 'Hi hi Hello Hi hi');
+
+ // Then
+ System.assertEquals('hello', actualValue);
+ }
+
+ @isTest
+ static void whenVerifyMultipleCallsWithMatchersShouldReturnCorrectMethodCallCounts()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).add(fflib_Match.anyString());
+ ((fflib_MyList.IList) mocks.verify(mockList)).add('fred');
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(fflib_Match.stringContains('fred'));
+ }
+
+ @isTest
+ static void whenStubExceptionWithMatchersShouldThrowException()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ ((fflib_MyList.IList) mocks.doThrowWhen(new MyException('Matcher Exception'), mockList)).add(fflib_Match.stringContains('Hello'));
+ mocks.stopStubbing();
+
+ // When
+ mockList.add('Hi');
+
+ try
+ {
+ mockList.add('Hi Hello Hi');
+ System.assert(false, 'Expected exception');
+ }
+ catch (MyException e)
+ {
+ //Then
+ System.assertEquals('Matcher Exception', e.getMessage());
+ }
+ }
+
+ @isTest
+ static void whenVerifyWithCombinedMatchersShouldReturnCorrectMethodCallCounts()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, 0)).add(
+ (String)fflib_Match.allOf(fflib_Match.eq('bob'), fflib_Match.stringContains('re'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(
+ (String)fflib_Match.allOf(fflib_Match.eq('fred'), fflib_Match.stringContains('re'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).add(
+ (String)fflib_Match.anyOf(fflib_Match.eq('bob'), fflib_Match.eq('fred'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, 1)).add(
+ (String)fflib_Match.anyOf(fflib_Match.eq('bob'), fflib_Match.eq('jack'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).add(
+ (String)fflib_Match.noneOf(fflib_Match.eq('jack'), fflib_Match.eq('tim'))
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).add(
+ (String)fflib_Match.noneOf(
+ fflib_Match.anyOf(fflib_Match.eq('jack'), fflib_Match.eq('jill')),
+ fflib_Match.allOf(fflib_Match.eq('tim'), fflib_Match.stringContains('i'))
+ )
+ );
+
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).add(
+ (String)fflib_Match.isNot(fflib_Match.eq('jack'))
+ );
+ }
+
+ @isTest
+ static void whenStubCustomMatchersCanBeUsed()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get((Integer)fflib_Match.matches(new isOdd()))).thenReturn('Odd');
+ mocks.when(mockList.get((Integer)fflib_Match.matches(new isEven()))).thenReturn('Even');
+ mocks.stopStubbing();
+
+ // When
+ String s1 = mockList.get(1);
+ String s2 = mockList.get(2);
+ String s3 = mockList.get(3);
+ String s4 = mockList.get(4);
+ String s5 = mockList.get(5);
+
+ // Then
+ System.assertEquals('Odd', s1);
+ System.assertEquals('Even', s2);
+ System.assertEquals('Odd', s3);
+ System.assertEquals('Even', s4);
+ System.assertEquals('Odd', s5);
+ }
+
+ @isTest
+ static void whenVerifyCustomMatchersCanBeUsed()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.get(1);
+ mockList.get(2);
+ mockList.get(3);
+ mockList.get(4);
+ mockList.get(5);
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, 3)).get((Integer)fflib_Match.matches(new isOdd()));
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).get((Integer)fflib_Match.matches(new isEven()));
+ }
+
+ @isTest
+ static void whenStubWithMatcherAndNonMatcherArgumentsShouldThrowException()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ String expectedError = 'The number of matchers defined (1).'
+ + ' does not match the number expected (2)\n'
+ + 'If you are using matchers all arguments must be passed in as matchers.\n'
+ + 'For example myList.add(fflib_Match.anyInteger(), \'String\') should be defined as myList.add(fflib_Match.anyInteger(), fflib_Match.eq(\'String\')).';
+
+ // Then
+ try
+ {
+ mocks.startStubbing();
+ mocks.when(mockList.get2(fflib_Match.anyInteger(), 'String literal')).thenReturn('fail');
+ System.assert(false, 'Expected exception');
+ }
+ catch (fflib_ApexMocks.ApexMocksException e)
+ {
+ System.assertEquals(expectedError, e.getMessage());
+ }
+ }
+
+ @isTest
+ static void whenVerifyWithMatcherAndNonMatcherArgumentsShouldThrowException()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ String expectedError = 'The number of matchers defined (1).'
+ + ' does not match the number expected (2)\n'
+ + 'If you are using matchers all arguments must be passed in as matchers.\n'
+ + 'For example myList.add(fflib_Match.anyInteger(), \'String\') should be defined as myList.add(fflib_Match.anyInteger(), fflib_Match.eq(\'String\')).';
+
+ mockList.get2(1, 'String literal');
+
+ // Then
+ try
+ {
+ ((fflib_MyList.IList) mocks.verify(mockList)).get2(fflib_Match.anyInteger(), 'String literal');
+ System.assert(false, 'Expected exception');
+ }
+ catch (fflib_ApexMocks.ApexMocksException e)
+ {
+ System.assertEquals(expectedError, e.getMessage());
+ }
+ }
+
+ @isTest
+ static void whenStubSameMethodWithMatchersAndNonMatchersShouldStubInOrder()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+
+ mocks.when(mockList.get2(1, 'Non-matcher first')).thenReturn('Bad'); //Set the return value using the non-matcher arguments
+ mocks.when(mockList.get2(fflib_Match.eqInteger(1), fflib_Match.stringContains('Non-matcher first'))).thenReturn('Good'); //Override the return value using matcher arguments
+
+ mocks.when(mockList.get2(fflib_Match.eqInteger(1), fflib_Match.stringContains('Matcher first'))).thenReturn('Bad'); //Set the return value using the matcher arguments
+ mocks.when(mockList.get2(1, 'Matcher first')).thenReturn('Good'); //Override the return value using non-matcher arguments
+
+ mocks.stopStubbing();
+
+ // When/Thens
+ System.assertEquals('Good', mockList.get2(1, 'Non-matcher first'));
+ System.assertEquals('Good', mockList.get2(1, 'Matcher first'));
+ }
+
+ @isTest
+ static void whenStubExceptionSameMethodWithMatchersAndNonMatchersShouldStubInOrder()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+
+ ((fflib_MyList.IList)mocks.doThrowWhen(new fflib_ApexMocks.ApexMocksException('Bad'), mockList)).add('Non-matcher first'); //Set the exception value using the non-matcher arguments
+ ((fflib_MyList.IList)mocks.doThrowWhen(new fflib_ApexMocks.ApexMocksException('Good'), mockList)).add(fflib_Match.stringContains('Non-matcher first')); //Override the exception value using matcher arguments
+
+ ((fflib_MyList.IList)mocks.doThrowWhen(new fflib_ApexMocks.ApexMocksException('Bad'), mockList)).add(fflib_Match.stringContains('Matcher first')); //Set the exception value using the matcher arguments
+ ((fflib_MyList.IList)mocks.doThrowWhen(new fflib_ApexMocks.ApexMocksException('Good'), mockList)).add('Matcher first'); //Override the exception value using non-matcher arguments
+
+ mocks.stopStubbing();
+
+ // When/Thens
+ try
+ {
+ mockList.add('Non-matcher first');
+ System.assert(false, 'Expected exception');
+ }
+ catch (fflib_ApexMocks.ApexMocksException e)
+ {
+ System.assertEquals('Good', e.getMessage());
+ }
+
+ try
+ {
+ mockList.add('Matcher first');
+ System.assert(false, 'Expected exception');
+ }
+ catch (fflib_ApexMocks.ApexMocksException e)
+ {
+ System.assertEquals('Good', e.getMessage());
+ }
+ }
+
+ @isTest
+ static void whenStubSingleCallWithSingleArgumentShouldReturnStubbedValue()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get(0)).thenReturn('bob');
+ mocks.stopStubbing();
+
+ // When
+ String actualValue = mockList.get(0);
+
+ // Then
+ System.assertEquals('bob', actualValue);
+ }
+
+ @isTest
+ static void whenStubSingleCallWithNullReturnValueItShouldReturnNull()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get(0)).thenReturn(null);
+ mocks.stopStubbing();
+
+ // When
+ String actualValue = mockList.get(0);
+
+ // Then
+ System.assertEquals(null, actualValue);
+ }
+
+ @isTest
+ static void whenStubMultipleCallsWithSingleArgumentShouldReturnStubbedValues()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get(0)).thenReturn('bob');
+ mocks.when(mockList.get(1)).thenReturn('fred');
+ mocks.stopStubbing();
+
+ // When
+ String actualValueArg0 = mockList.get(0);
+ String actualValueArg1 = mockList.get(1);
+ String actualValueArg2 = mockList.get(2);
+
+ // Then
+ System.assertEquals('bob', actualValueArg0);
+ System.assertEquals('fred', actualValueArg1);
+ System.assertEquals(null, actualValueArg2);
+ }
+
+ @isTest
+ static void whenStubSameCallWithDifferentArgumentValueShouldReturnLastStubbedValue()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get(0)).thenReturn('bob1');
+ mocks.when(mockList.get(0)).thenReturn('bob2');
+ mocks.stopStubbing();
+
+ // When
+ String actualValue = mockList.get(0);
+
+ // Then
+ System.assertEquals('bob2', actualValue);
+ }
+
+ @isTest
+ static void whenStubCallWithNoArgumentsShouldReturnStubbedValue()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.isEmpty()).thenReturn(false);
+ mocks.stopStubbing();
+
+ // When
+ Boolean actualValue = mockList.isEmpty();
+
+ // Then
+ System.assertEquals(false, actualValue);
+ }
+
+ @isTest
+ static void verifySingleMethodCallWithNoArguments()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.isEmpty();
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList)).isEmpty();
+ }
+
+ @isTest
+ static void verifySingleMethodCallWithSingleArgument()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList)).add('bob');
+ }
+
+ @isTest
+ static void verifyMultipleMethodCallsWithSameSingleArgument()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).add('bob');
+ }
+
+ @isTest
+ static void verifyMultipleMethodCallsWithDifferentSingleArgument()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add('fred');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList)).add('bob');
+ ((fflib_MyList.IList) mocks.verify(mockList)).add('fred');
+ }
+
+ @isTest
+ static void verifyMethodCallsWithSameNameButDifferentArgumentTypes()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+ mockList.add(new String[] {'bob'});
+ mockList.add((String)null);
+ mockList.add((String[])null);
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList)).add('bob');
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(new String[] {'bob'});
+ ((fflib_MyList.IList) mocks.verify(mockList)).add((String)null);
+ ((fflib_MyList.IList) mocks.verify(mockList)).add((String[])null);
+ }
+
+ @isTest
+ static void verifyMethodNotCalled()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.get(0);
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, fflib_ApexMocks.NEVER)).add('bob');
+ ((fflib_MyList.IList) mocks.verify(mockList)).get(0);
+ }
+
+ @isTest
+ static void stubAndVerifyMethodCallsWithNoArguments()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.isEmpty()).thenReturn(false);
+ mocks.stopStubbing();
+
+ mockList.clear();
+
+ // When
+ Boolean actualValue = mockList.isEmpty();
+
+ // Then
+ System.assertEquals(false, actualValue);
+ ((fflib_MyList.IList) mocks.verify(mockList)).clear();
+ }
+
+ @isTest
+ static void whenStubExceptionTheExceptionShouldBeThrown()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get(0)).thenThrow(new MyException('Stubbed exception.'));
+ mocks.stopStubbing();
+
+ // When
+ try
+ {
+ mockList.get(0);
+ System.assert(false, 'Stubbed exception should have been thrown.');
+ }
+ catch(Exception e)
+ {
+ // Then
+ System.assert(e instanceof MyException);
+ System.assertEquals('Stubbed exception.', e.getMessage());
+ }
+ }
+
+ @isTest
+ static void whenStubVoidMethodWithExceptionThenExceptionShouldBeThrown()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ ((fflib_MyList.IList) mocks.doThrowWhen(new MyException('Stubbed exception.'), mockList)).clear();
+ mocks.stopStubbing();
+
+ // When
+ try
+ {
+ mockList.clear();
+ System.assert(false, 'Stubbed exception should have been thrown.');
+ }
+ catch(Exception e)
+ {
+ // Then
+ System.assert(e instanceof MyException);
+ System.assertEquals('Stubbed exception.', e.getMessage());
+ }
+ }
+
+ @isTest
+ static void whenStubMultipleVoidMethodsWithExceptionsThenExceptionsShouldBeThrown()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ ((fflib_MyList.IList) mocks.doThrowWhen(new MyException('clear stubbed exception.'), mockList)).clear();
+ ((fflib_MyList.IList) mocks.doThrowWhen(new MyException('add stubbed exception.'), mockList)).add('bob');
+ mocks.stopStubbing();
+
+ // When
+ try
+ {
+ mockList.clear();
+ System.assert(false, 'Stubbed exception should have been thrown.');
+ }
+ catch(Exception e)
+ {
+ // Then
+ System.assert(e instanceof MyException);
+ System.assertEquals('clear stubbed exception.', e.getMessage());
+ }
+
+ // When
+ try
+ {
+ mockList.add('bob');
+ System.assert(false, 'Stubbed exception should have been thrown.');
+ }
+ catch(Exception e)
+ {
+ // Then
+ System.assert(e instanceof MyException);
+ System.assertEquals('add stubbed exception.', e.getMessage());
+ }
+ }
+
+ @isTest
+ static void whenStubVoidMethodWithExceptionAndCallMethodTwiceThenExceptionShouldBeThrownTwice()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ ((fflib_MyList.IList) mocks.doThrowWhen(new MyException('clear stubbed exception.'), mockList)).clear();
+ mocks.stopStubbing();
+
+ // When
+ try
+ {
+ mockList.clear();
+ System.assert(false, 'Stubbed exception should have been thrown.');
+ }
+ catch(Exception e)
+ {
+ // Then
+ System.assert(e instanceof MyException);
+ System.assertEquals('clear stubbed exception.', e.getMessage());
+ }
+
+ // When
+ try
+ {
+ mockList.clear();
+ System.assert(false, 'Stubbed exception should have been thrown.');
+ }
+ catch(Exception e)
+ {
+ // Then
+ System.assert(e instanceof MyException);
+ System.assertEquals('clear stubbed exception.', e.getMessage());
+ }
+ }
+
+ @isTest
+ static void verifyMethodCallWhenNoCallsBeenMadeForType()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, fflib_ApexMocks.NEVER)).add('bob');
+ }
+
+ @isTest
+ static void verifySingleMethodCallWithMultipleArguments()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.set(0, 'bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList)).set(0, 'bob');
+ ((fflib_MyList.IList) mocks.verify(mockList, fflib_ApexMocks.NEVER)).set(0, 'fred');
+ }
+
+ @isTest
+ static void whenStubMultipleCallsWithMultipleArgumentShouldReturnStubbedValues()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(mockList.get2(0, 'zero')).thenReturn('bob');
+ mocks.when(mockList.get2(1, 'one')).thenReturn('fred');
+ mocks.when(mockList.get2(0, 'two')).thenReturn('bob');
+ mocks.when(mockList.get2(1, 'three')).thenReturn('bub');
+ mocks.stopStubbing();
+
+ // When
+ String actualValueArg0 = mockList.get2(0, 'zero');
+ String actualValueArg1 = mockList.get2(1, 'one');
+ String actualValueArg2 = mockList.get2(0, 'two');
+ String actualValueArg3 = mockList.get2(1, 'three');
+ String actualValueArg4 = mockList.get2(0, 'three');
+
+ // Then
+ System.assertEquals('bob', actualValueArg0);
+ System.assertEquals('fred', actualValueArg1);
+ System.assertEquals('bob', actualValueArg2);
+ System.assertEquals('bub', actualValueArg3);
+ System.assertEquals(null, actualValueArg4);
+ }
+
+ @isTest
+ static void whenStubNullConcreteArgValueCorrectValueIsReturned()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ String expected = 'hello';
+
+ mocks.startStubbing();
+ mocks.when(mockList.get(null)).thenReturn(expected);
+ mocks.stopStubbing();
+
+ // When
+ String actual = mockList.get(null);
+
+ // Then
+ System.assertEquals(expected, actual);
+ }
+
+ @isTest
+ static void whenSetDoThrowWhenExceptionsValuesAreSet()
+ {
+ //Given
+ MyException e = new MyException('Test');
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+
+ List expsList = new List{e};
+
+ //When
+ mocks.DoThrowWhenExceptions = expsList;
+
+ //Then
+ System.assert(expsList === mocks.DoThrowWhenExceptions);
+ }
+
+ @isTest
+ static void whenVerifyMethodNeverCalledMatchersAreReset()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, fflib_ApexMocks.NEVER)).get(fflib_Match.anyInteger());
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(fflib_Match.anyString());
+ }
+
+ @isTest
+ static void whenMockIsGeneratedCanVerify()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList.IList mockList = new fflib_Mocks.Mockfflib_MyList(mocks);
+
+ // When
+ mockList.add('bob');
+
+ // Then
+ ((fflib_MyList.IList) mocks.verify(mockList, fflib_ApexMocks.NEVER)).get(fflib_Match.anyInteger());
+ ((fflib_MyList.IList) mocks.verify(mockList)).add('bob');
+ }
+
+ @isTest
+ static void whenMockIsGeneratedCanStubVerify()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList.IList mockList = new fflib_Mocks.Mockfflib_MyList(mocks);
+
+ // When
+ mocks.startStubbing();
+ mocks.when(mockList.get(1)).thenReturn('One');
+ mocks.when(mockList.get(fflib_Match.integerMoreThan(2))).thenReturn('>Two');
+ mocks.stopStubbing();
+
+ // Then
+ System.assertEquals(null, mockList.get(0));
+ System.assertEquals('One', mockList.get(1));
+ System.assertEquals(null, mockList.get(2));
+ System.assertEquals('>Two', mockList.get(3));
+ }
+
+ @isTest
+ static void thatMultipleInstancesCanBeMockedIndependently()
+ {
+ fflib_ApexMocksConfig.HasIndependentMocks = true;
+
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList first = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_MyList second = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(first.get(0)).thenReturn('First');
+ mocks.when(second.get(0)).thenReturn('Second');
+ mocks.stopStubbing();
+
+ // When
+ String actual = first.get(0);
+
+ // Then
+ System.assertEquals('First', actual, 'Should have returned stubbed value');
+ ((fflib_MyList)mocks.verify(first)).get(0);
+ ((fflib_MyList)mocks.verify(second, mocks.never())).get(0);
+ }
+
+ @isTest
+ static void thatMultipleInstancesCanBeMockedDependently()
+ {
+ fflib_ApexMocksConfig.HasIndependentMocks = false;
+
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList first = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_MyList second = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ mocks.startStubbing();
+ mocks.when(first.get(0)).thenReturn('First');
+ mocks.when(second.get(0)).thenReturn('Second');
+ mocks.stopStubbing();
+
+ // When
+ String actual = first.get(0);
+
+ // Then
+ System.assertEquals('Second', actual, 'Should have returned stubbed value');
+ ((fflib_MyList)mocks.verify(first)).get(0);
+ ((fflib_MyList)mocks.verify(second)).get(0);
+ }
+
+ static void thatStubbingCanBeChainedFirstExceptionThenValue()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(new MyException('Stubbed exception.')).thenReturn('One');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('Stubbed exception.');
+ assertReturnedValue('One');
+ }
+
+ @isTest
+ static void thatStubbingCanBeChainedFirstValueThenException()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('One').thenThrow(new MyException('Stubbed exception.'));
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('One');
+ assertExceptionMessage('Stubbed exception.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleMethodsCanBeChainedFirstExceptionThenValue()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(new MyException('Stubbed exception.')).thenReturn('One');
+ MY_MOCKS.when(MY_MOCK_LIST.get2(2, 'Hello.')).thenThrow(new MyException('Stubbed exception2.')).thenReturn('One2');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('Stubbed exception.');
+ assertReturnedValue('One');
+ assertExceptionMessageForGet2('Stubbed exception2.');
+ assertReturnedValueForGet2('One2');
+ }
+
+ @isTest
+ static void thatStubbingMultipleMethodsCanBeChainedFirstValueThenException()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('One').thenThrow(new MyException('Stubbed exception.'));
+ MY_MOCKS.when(MY_MOCK_LIST.get2(2, 'Hello.')).thenReturn('One2').thenThrow(new MyException('Stubbed exception2.'));
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('One');
+ assertExceptionMessage('Stubbed exception.');
+ assertReturnedValueForGet2('One2');
+ assertExceptionMessageForGet2('Stubbed exception2.');
+ }
+
+ @isTest
+ static void thatStubbingReturnsDifferentValuesForDifferentCalls()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('One');
+ assertReturnedValue('Two');
+ assertReturnedValue('Three');
+ }
+
+ @isTest
+ static void thatStubbingReturnsDifferentValuesForDifferentCallsAndRepeatLastValuesForFurtherCalls()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('One');
+ assertReturnedValue('Two');
+ assertReturnedValue('Three');
+
+ assertReturnedValue('Three');
+ assertReturnedValue('Three');
+ }
+
+ @isTest
+ static void thatStubbingThrowsDifferentExceptionsForDifferentCalls()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('second.');
+ assertExceptionMessage('third.');
+ }
+
+ @isTest
+ static void thatStubbingThrowsDifferentExceptionsForDifferentCallsAndRepeatLastExceptionForFurtherCalls()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('second.');
+ assertExceptionMessage('third.');
+
+ assertExceptionMessage('third.');
+ assertExceptionMessage('third.');
+ }
+
+ @isTest
+ static void thatStubbingThrowsAndReturnsDifferentExceptionsAndValuesForDifferentCalls()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).
+ thenThrowMulti(new List{first, second, third}).
+ thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('second.');
+ assertExceptionMessage('third.');
+
+ assertReturnedValue('One');
+ assertReturnedValue('Two');
+ assertReturnedValue('Three');
+
+ assertReturnedValue('Three');
+ assertReturnedValue('Three');
+ }
+
+ @isTest
+ static void thatStubbingReturnsAndThrowsDifferentValuesAndExceptionsForDifferentCalls()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).
+ thenReturnMulti(new List{'One', 'Two', 'Three'}).
+ thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('One');
+ assertReturnedValue('Two');
+ assertReturnedValue('Three');
+
+ assertExceptionMessage('first.');
+ assertExceptionMessage('second.');
+ assertExceptionMessage('third.');
+
+ assertExceptionMessage('third.');
+ assertExceptionMessage('third.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenReturnWithSingleValue()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('One');
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('Two');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('Two');
+ assertReturnedValue('Two');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenReturnMultiWithSingleValue()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('Two');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('Two');
+ assertReturnedValue('Two');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenReturnMultiWithMultiValue()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'Four', 'Five', 'Six'});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('Four');
+ assertReturnedValue('Five');
+ assertReturnedValue('Six');
+
+ assertReturnedValue('Six');
+ assertReturnedValue('Six');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenReturnWithMultiValues()
+ {
+ // Given
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('Two');
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('One');
+ assertReturnedValue('Two');
+ assertReturnedValue('Three');
+
+ assertReturnedValue('Three');
+ assertReturnedValue('Three');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenReturnMultiWithSingleException()
+ {
+ // Given
+ MyException first = new MyException('first.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(first);
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('first.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenReturnMultiWithMultiExceptions()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('second.');
+ assertExceptionMessage('third.');
+
+ assertExceptionMessage('third.');
+ assertExceptionMessage('third.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenReturnWithMultiExceptions()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('Two');
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('second.');
+ assertExceptionMessage('third.');
+
+ assertExceptionMessage('third.');
+ assertExceptionMessage('third.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenReturnWithSingleException()
+ {
+ // Given
+ MyException first = new MyException('first.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('Two');
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(first);
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('first.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenThrowWithSingleValue()
+ {
+ // Given
+ MyException first = new MyException('first.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(first);
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('Two');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('Two');
+ assertReturnedValue('Two');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenThrowMultiWithSingleValue()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn('Two');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('Two');
+ assertReturnedValue('Two');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenThrowMultiWithMultiValue()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'Four', 'Five', 'Six'});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('Four');
+ assertReturnedValue('Five');
+ assertReturnedValue('Six');
+
+ assertReturnedValue('Six');
+ assertReturnedValue('Six');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenThrowWithMultiValues()
+ {
+ // Given
+ MyException first = new MyException('first.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(first);
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List{'One', 'Two', 'Three'});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertReturnedValue('One');
+ assertReturnedValue('Two');
+ assertReturnedValue('Three');
+
+ assertReturnedValue('Three');
+ assertReturnedValue('Three');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenThrowMultiWithSingleException()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+ MyException fourth = new MyException('fourth.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(fourth);
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('fourth.');
+ assertExceptionMessage('fourth.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenThrowMultiWithMultiExceptions()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ MyException fourth = new MyException('fourth.');
+ MyException fifth = new MyException('fifth.');
+ MyException sixth = new MyException('sixth.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{fourth, fifth, sixth});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('fourth.');
+ assertExceptionMessage('fifth.');
+ assertExceptionMessage('sixth.');
+
+ assertExceptionMessage('sixth.');
+ assertExceptionMessage('sixth.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenThrowWithMultiExceptions()
+ {
+ // Given
+ MyException beforeFirst = new MyException('before first.');
+
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(beforeFirst);
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List{first, second, third});
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('second.');
+ assertExceptionMessage('third.');
+
+ assertExceptionMessage('third.');
+ assertExceptionMessage('third.');
+ }
+
+ @isTest
+ static void thatStubbingMultipleTimesOverridePreviousThenThrowWithSingleException()
+ {
+ // Given
+ MyException beforeFirst = new MyException('before first.');
+ MyException first = new MyException('first.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(beforeFirst);
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(first);
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessage('first.');
+ assertExceptionMessage('first.');
+ }
+
+ @isTest
+ static void thatVoidMethodThrowsMultipleExceptions()
+ {
+ // Given
+ MyException beforeFirst = new MyException('before first.');
+
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ ((fflib_MyList.IList) MY_MOCKS.doThrowWhen(new List{first, second, third}, MY_MOCK_LIST)).add('Hello');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessageOnVoidMethod('first.');
+ assertExceptionMessageOnVoidMethod('second.');
+ assertExceptionMessageOnVoidMethod('third.');
+
+ assertExceptionMessageOnVoidMethod('third.');
+ assertExceptionMessageOnVoidMethod('third.');
+ }
+
+ @isTest
+ static void thatMultipleVoidMethodsThrowsMultipleExceptions()
+ {
+ // Given
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ MyException first2 = new MyException('first2.');
+ MyException second2 = new MyException('second2.');
+ MyException third2 = new MyException('third2.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ ((fflib_MyList.IList) MY_MOCKS.doThrowWhen(new List{first2, second2, third2}, MY_MOCK_LIST)).addMore('Hello');
+ ((fflib_MyList.IList) MY_MOCKS.doThrowWhen(new List{first, second, third}, MY_MOCK_LIST)).add('Hello');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessageOnVoidMethod('first.');
+ assertExceptionMessageOnVoidMethod('second.');
+ assertExceptionMessageOnVoidMethod('third.');
+
+ assertExceptionMessageOnVoidMethod('third.');
+ assertExceptionMessageOnVoidMethod('third.');
+
+
+ assertExceptionMessageOnAddMoreVoidMethod('first2.');
+ assertExceptionMessageOnAddMoreVoidMethod('second2.');
+ assertExceptionMessageOnAddMoreVoidMethod('third2.');
+
+ assertExceptionMessageOnAddMoreVoidMethod('third2.');
+ assertExceptionMessageOnAddMoreVoidMethod('third2.');
+ }
+
+ @isTest
+ static void thatStubbingMutipleTimesVoidMethodThrowsMultipleExceptionsOverride()
+ {
+ // Given
+ MyException beforeFirst = new MyException('before first.');
+
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ MyException fourth = new MyException('fourth.');
+ MyException fifth = new MyException('fifth.');
+ MyException sixth = new MyException('sixth.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ ((fflib_MyList.IList) MY_MOCKS.doThrowWhen(new List{first, second, third}, MY_MOCK_LIST)).add('Hello');
+ ((fflib_MyList.IList) MY_MOCKS.doThrowWhen(new List{fourth, fifth, sixth}, MY_MOCK_LIST)).add('Hello');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessageOnVoidMethod('fourth.');
+ assertExceptionMessageOnVoidMethod('fifth.');
+ assertExceptionMessageOnVoidMethod('sixth.');
+
+ assertExceptionMessageOnVoidMethod('sixth.');
+ assertExceptionMessageOnVoidMethod('sixth.');
+ }
+
+ @isTest
+ static void thatStubbingMutipleTimesVoidMethodThrowsMultipleExceptionsOverrideWithSingleException()
+ {
+ // Given
+ MyException beforeFirst = new MyException('before first.');
+
+ MyException first = new MyException('first.');
+ MyException second = new MyException('second.');
+ MyException third = new MyException('third.');
+
+ MyException fourth = new MyException('fourth.');
+ MyException fifth = new MyException('fifth.');
+ MyException sixth = new MyException('sixth.');
+
+ // When
+ MY_MOCKS.startStubbing();
+ ((fflib_MyList.IList) MY_MOCKS.doThrowWhen(new List{first, second, third}, MY_MOCK_LIST)).add('Hello');
+ ((fflib_MyList.IList) MY_MOCKS.doThrowWhen(fourth, MY_MOCK_LIST)).add('Hello');
+ MY_MOCKS.stopStubbing();
+
+ // Then
+ assertExceptionMessageOnVoidMethod('fourth.');
+ assertExceptionMessageOnVoidMethod('fourth.');
+ }
+
+ @isTest
+ static void thatExceptionIsthrownWhenStubbingIsNotDone()
+ {
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1));
+ MY_MOCKS.stopStubbing();
+
+ try
+ {
+ MY_MOCK_LIST.get(1);
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(fflib_ApexMocks.ApexMocksException myex)
+ {
+ System.assertEquals(
+ 'The stubbing is not correct, no return values have been set.',
+ myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ @isTest
+ static void thatExceptionIsthrownWhenReturnMultiPassEmptyList()
+ {
+ try
+ {
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(new List());
+ MY_MOCKS.stopStubbing();
+ System.assert(false, 'an exception was expected');
+ }
+ catch(fflib_ApexMocks.ApexMocksException myex)
+ {
+ System.assertEquals(
+ 'The stubbing is not correct, no return values have been set.',
+ myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ @isTest
+ static void thatExceptionIsthrownWhenReturnMultiPassNullList()
+ {
+ try
+ {
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturnMulti(null);
+ MY_MOCKS.stopStubbing();
+ System.assert(false, 'an exception was expected');
+ }
+ catch(fflib_ApexMocks.ApexMocksException myex)
+ {
+ System.assertEquals(
+ 'The stubbing is not correct, no return values have been set.',
+ myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ @isTest
+ static void thatExceptionIsthrownWhenThrowMultiPassEmptyList()
+ {
+ try
+ {
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(new List());
+ MY_MOCKS.stopStubbing();
+ System.assert(false, 'an exception was expected');
+ }
+ catch(fflib_ApexMocks.ApexMocksException myex)
+ {
+ System.assertEquals(
+ 'The stubbing is not correct, no return values have been set.',
+ myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ @isTest
+ static void thatExceptionIsthrownWhenThrowMultiPassNullList()
+ {
+ try
+ {
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrowMulti(null);
+ MY_MOCKS.stopStubbing();
+
+ System.assert(false, 'an exception was expected');
+ }
+ catch(fflib_ApexMocks.ApexMocksException myex)
+ {
+ System.assertEquals(
+ 'The stubbing is not correct, no return values have been set.',
+ myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ @isTest
+ static void thatNullCanBeUsedAsReturnValue()
+ {
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenReturn(null);
+ MY_MOCKS.stopStubbing();
+
+
+ System.assertEquals(null, MY_MOCK_LIST.get(1), 'it should be possible stub using the null value');
+ }
+
+ @isTest
+ static void thatNullCanBeUsedAsExceptionvalue()
+ {
+ MY_MOCKS.startStubbing();
+ MY_MOCKS.when(MY_MOCK_LIST.get(1)).thenThrow(null);
+ MY_MOCKS.stopStubbing();
+
+ System.assertEquals(null, MY_MOCK_LIST.get(1), 'it should be possible stub using the null value');
+ }
+
+
+
+ private static void assertExceptionMessage(String expectedMessage)
+ {
+ try
+ {
+ MY_MOCK_LIST.get(1);
+ System.assert(false, 'an exception was expected');
+ }
+ catch(MyException myex)
+ {
+ System.assertEquals(expectedMessage, myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ private static void assertExceptionMessageForGet2(String expectedMessage)
+ {
+ try
+ {
+ MY_MOCK_LIST.get2(2, 'Hello.');
+ System.assert(false, 'an exception was expected');
+ }
+ catch(MyException myex)
+ {
+ System.assertEquals(expectedMessage, myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ private static void assertExceptionMessageOnVoidMethod(String expectedMessage)
+ {
+ try
+ {
+ MY_MOCK_LIST.add('Hello');
+ System.assert(false, 'an exception was expected');
+ }
+ catch(MyException myex)
+ {
+ System.assertEquals(expectedMessage, myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ private static void assertExceptionMessageOnAddMoreVoidMethod(String expectedMessage)
+ {
+ try
+ {
+ MY_MOCK_LIST.addMore('Hello');
+ System.assert(false, 'an exception was expected');
+ }
+ catch(MyException myex)
+ {
+ System.assertEquals(expectedMessage, myex.getMessage(), 'the message reported by the exception is not correct');
+ }
+ }
+
+ private static void assertReturnedValue(String expectedValue)
+ {
+ System.assertEquals(expectedValue, MY_MOCK_LIST.get(1), 'the method did not returned the expected value');
+ }
+
+ private static void assertReturnedValueForGet2(String expectedValue)
+ {
+ System.assertEquals(expectedValue, MY_MOCK_LIST.get2(2, 'Hello.'), 'the method did not returned the expected value');
+ }
+
+ @isTest
+ static void thatToStringReturnsSimpleStringValue()
+ {
+ System.assertEquals('fflib_ApexMocks', '' + new fflib_ApexMocks());
+ }
+
+ private class MyException extends Exception
+ {
+ }
+
+ private class isOdd implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg instanceof Integer ? Math.mod((Integer)arg, 2) == 1: false;
+ }
+ }
+
+ private class isEven implements fflib_IMatcher
+ {
+ public Boolean matches(Object arg)
+ {
+ return arg instanceof Integer ? Math.mod((Integer)arg, 2) == 0: false;
+ }
+ }
+}
diff --git a/sfdx-source/apex-mocks/tests/fflib_ApexMocksTest.cls-meta.xml b/sfdx-source/apex-mocks/tests/fflib_ApexMocksTest.cls-meta.xml
new file mode 100644
index 00000000000..dd61d1f917e
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_ApexMocksTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 52.0
+ Active
+
diff --git a/sfdx-source/apex-mocks/tests/fflib_ApexMocksUtilsTest.cls b/sfdx-source/apex-mocks/tests/fflib_ApexMocksUtilsTest.cls
new file mode 100644
index 00000000000..5cdef400f47
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_ApexMocksUtilsTest.cls
@@ -0,0 +1,261 @@
+/**
+ * Copyright (c) 2014-2016, FinancialForce.com, inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the FinancialForce.com, inc nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+@isTest
+public class fflib_ApexMocksUtilsTest
+{
+ public static Schema.FieldSet findAnyFieldSet()
+ {
+ for (Schema.SObjectType objectType : Schema.getGlobalDescribe().values())
+ {
+ for (Schema.FieldSet fs : objectType.getDescribe().FieldSets.getMap().values())
+ {
+ return fs;
+ }
+ }
+
+ return null;
+ }
+
+ @isTest
+ private static void makeRelationship_returnsObjectsWithRelationFieldSet()
+ {
+ //Given
+ Account acc = new Account(
+ Id = fflib_IDGenerator.generate(Account.SObjectType),
+ Name = 'AccName',
+ NumberOfEmployees = 7
+ );
+
+ Contact contact1 = new Contact(
+ Id = fflib_IDGenerator.generate(Contact.SObjectType),
+ DoNotCall = true
+ );
+
+ Contact contact2 = new Contact(
+ Id = fflib_IDGenerator.generate(Contact.SObjectType),
+ DoNotCall = false
+ );
+
+ //When
+ Account accWithRelationships = ((List)fflib_ApexMocksUtils.makeRelationship(
+ List.class,
+ new List { acc },
+ Contact.AccountId,
+ new List> { new List { contact1, contact2 }}
+ ))[0];
+
+ //Then
+ System.assertEquals(acc.Id, accWithRelationships.Id);
+ System.assertEquals(acc.Name, accWithRelationships.Name);
+ System.assertEquals(acc.NumberOfEmployees, accWithRelationships.NumberOfEmployees);
+
+ //Assert relationship fields
+ List contacts = accWithRelationships.Contacts;
+ System.assertNotEquals(null, contacts);
+ System.assertEquals(2, contacts.size());
+
+ System.assertEquals(contact1.Id, contacts[0].Id);
+ System.assertEquals(contact1.DoNotCall, contacts[0].DoNotCall);
+
+ System.assertEquals(contact2.Id, contacts[1].Id);
+ System.assertEquals(contact2.DoNotCall, contacts[1].DoNotCall);
+ }
+
+ @isTest
+ private static void makeRelationship_GenericOverload_ReturnsObjectsWithRelationFieldSet() {
+ //Given
+ SObject acc = Schema.getGlobalDescribe().get('Account').newSObject();
+ acc.put('Id', fflib_IDGenerator.generate(acc.getSObjectType()));
+ acc.put('Name', 'AccName');
+ acc.put('NumberOfEmployees', 7);
+
+ SObject contact1 = Schema.getGlobalDescribe().get('Contact').newSObject();
+ contact1.put('Id', fflib_IDGenerator.generate(contact1.getSObjectType()));
+ contact1.put('DoNotCall', true);
+
+ SObject contact2 = Schema.getGlobalDescribe().get('Contact').newSObject();
+ contact2.put('Id', fflib_IDGenerator.generate(contact2.getSObjectType()));
+ contact2.put('DoNotCall', false);
+
+ //When
+ SObject accWithRelationships = ((List)fflib_ApexMocksUtils.makeRelationship(
+ 'Account',
+ 'Contact',
+ new List { acc },
+ 'AccountId',
+ new List> { new List { contact1, contact2 }}
+
+ ))[0];
+
+ //Then
+ System.assertEquals(acc.Id, accWithRelationships.Id);
+ System.assertEquals(acc.get('Name'), accWithRelationships.get('Name'));
+ System.assertEquals(acc.get('NumberOfEmployees'), accWithRelationships.get('NumberOfEmployees'));
+
+ //Assert relationship fields
+ List contacts = accWithRelationships.getSObjects('Contacts');
+ System.assertNotEquals(null, contacts);
+ System.assertEquals(2, contacts.size());
+
+ System.assertEquals(contact1.Id, contacts[0].Id);
+ System.assertEquals((Boolean)contact1.get('DoNotCall'), (Boolean)contacts[0].get('DoNotCall'));
+
+ System.assertEquals(contact2.Id, contacts[1].Id);
+ System.assertEquals((Boolean)contact2.get('DoNotCall'), (Boolean)contacts[1].get('DoNotCall'));
+ }
+
+ @isTest
+ private static void makeRelationship_GenericOverload_ThrowsErrorOnInvalidParentType() {
+
+ // Setup parent object
+ SObject acc = Schema.getGlobalDescribe().get('Account').newSObject();
+ acc.put('Id', fflib_IDGenerator.generate(acc.getSObjectType()));
+
+ // Setup child object
+ SObject cont = Schema.getGlobalDescribe().get('Contact').newSObject();
+ cont.put('Id', fflib_IDGenerator.generate(cont.getSObjectType()));
+
+ String errorMessage = '';
+ try {
+ // Call method under test
+ SObject accWithRelationships = ((List)fflib_ApexMocksUtils.makeRelationship(
+ 'MyInvalidParentType',
+ 'Contact',
+ new List { acc },
+ 'AccountId',
+ new List> { new List { cont }}
+ ))[0];
+ } catch (Exception exc) {
+ errorMessage = exc.getMessage();
+ }
+ System.assertEquals('SObject type not found: MyInvalidParentType', errorMessage);
+ }
+
+ @isTest
+ private static void makeRelationship_GenericOverload_ThrowsErrorOnInvalidChildType() {
+
+ // Setup parent object
+ SObject acc = Schema.getGlobalDescribe().get('Account').newSObject();
+ acc.put('Id', fflib_IDGenerator.generate(acc.getSObjectType()));
+
+ // Setup child object
+ SObject cont = Schema.getGlobalDescribe().get('Contact').newSObject();
+ cont.put('Id', fflib_IDGenerator.generate(cont.getSObjectType()));
+
+ String errorMessage = '';
+ try {
+ // Call method under test
+ SObject accWithRelationships = ((List)fflib_ApexMocksUtils.makeRelationship(
+ 'Account',
+ 'MyInvalidChildType',
+ new List { acc },
+ 'AccountId',
+ new List> { new List { cont }}
+ ))[0];
+ } catch (Exception exc) {
+ errorMessage = exc.getMessage();
+ }
+ System.assertEquals('SObject type not found: MyInvalidChildType', errorMessage);
+ }
+
+ @isTest
+ private static void makeRelationship_GenericOverload_ThrowsErrorOnInvalidFieldName() {
+
+ // Setup parent object
+ SObject acc = Schema.getGlobalDescribe().get('Account').newSObject();
+ acc.put('Id', fflib_IDGenerator.generate(acc.getSObjectType()));
+
+ // Setup child object
+ SObject cont = Schema.getGlobalDescribe().get('Contact').newSObject();
+ cont.put('Id', fflib_IDGenerator.generate(cont.getSObjectType()));
+
+ String errorMessage = '';
+ try {
+ // Call method under test
+ SObject accWithRelationships = ((List)fflib_ApexMocksUtils.makeRelationship(
+ 'Account',
+ 'Contact',
+ new List { acc },
+ 'MyInvalidField',
+ new List> { new List { cont }}
+ ))[0];
+ } catch (Exception exc) {
+ errorMessage = exc.getMessage();
+ }
+ System.assertEquals('SObject field not found: MyInvalidField', errorMessage);
+ }
+
+ @isTest
+ static void setReadOnlyFields_CreatedByIdSetToCurrentUserId_IdFieldSetSuccessfully() {
+
+ Account acc = new Account();
+ Id userId = fflib_IDGenerator.generate((new User()).getSObjectType());
+
+ Test.startTest();
+ acc = (Account)fflib_ApexMocksUtils.setReadOnlyFields(
+ acc,
+ Account.class,
+ new Map{Account.CreatedById => userId}
+ );
+ Test.stopTest();
+
+ System.assertEquals(userId, acc.CreatedById);
+ }
+
+ @isTest
+ static void setReadOnlyFields_LastReferencedDateSetOnAccount_DateTimeFieldSetSuccessfully() {
+
+ Account acc = new Account();
+ DateTime lastRefDate = DateTime.newInstanceGmt(2020, 1, 7, 23, 30, 0);
+
+ Test.startTest();
+ acc = (Account)fflib_ApexMocksUtils.setReadOnlyFields(
+ acc,
+ Account.class,
+ new Map {Account.LastReferencedDate => lastRefDate}
+ );
+ Test.stopTest();
+
+ System.assertEquals(lastRefDate, acc.LastReferencedDate);
+ }
+
+ @isTest
+ static void setReadOnlyFields_IsDeletedSetOnAccount_BooleanFieldSetSuccessfully() {
+
+ Account acc = new Account();
+ Boolean isDeleted = true;
+
+ Test.startTest();
+ acc = (Account)fflib_ApexMocksUtils.setReadOnlyFields(
+ acc,
+ Account.class,
+ new Map {Account.IsDeleted => isDeleted}
+ );
+ Test.stopTest();
+
+ System.assertEquals(isDeleted, acc.IsDeleted);
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-mocks/tests/fflib_ApexMocksUtilsTest.cls-meta.xml b/sfdx-source/apex-mocks/tests/fflib_ApexMocksUtilsTest.cls-meta.xml
new file mode 100644
index 00000000000..08c4dbd5cfa
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_ApexMocksUtilsTest.cls-meta.xml
@@ -0,0 +1,4 @@
+
+
+ 52.0
+
diff --git a/sfdx-source/apex-mocks/tests/fflib_ArgumentCaptorTest.cls b/sfdx-source/apex-mocks/tests/fflib_ArgumentCaptorTest.cls
new file mode 100644
index 00000000000..63c528bbd65
--- /dev/null
+++ b/sfdx-source/apex-mocks/tests/fflib_ArgumentCaptorTest.cls
@@ -0,0 +1,702 @@
+/*
+ * Copyright (c) 2016-2017 FinancialForce.com, inc. All rights reserved.
+ */
+/**
+ * @nodoc
+ */
+@isTest
+private class fflib_ArgumentCaptorTest
+{
+ @isTest
+ static void thatArgumentValueIsCaptured()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Fred');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+ ((fflib_MyList.IList) mocks.verify(mockList)).add((String) argument.capture());
+
+ System.assertEquals('Fred', (String)argument.getValue(), 'the argument captured is not as expected');
+ }
+
+ @isTest
+ static void thatCanPerformFurtherAssertionsOnCapturedArgumentValue()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ //When
+ TestInnerClass testValue = new TestInnerClass();
+ testValue.i = 4;
+ testValue.s = '5';
+
+ mockList.set(1, testValue);
+
+ //Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(TestInnerClass.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList)).set(fflib_Match.anyInteger(), argument.capture());
+
+ Object capturedArg = argument.getValue();
+ System.assertNotEquals(null, capturedArg, 'CapturedArg should not be null');
+
+ System.assert(capturedArg instanceof TestInnerClass, 'CapturedArg should be SObject, instead was ' + capturedArg);
+
+ TestInnerClass testValueCaptured = (TestInnerClass)capturedArg;
+
+ System.assertEquals(4, testValueCaptured.i, 'the values inside the argument captured should be the same of the original one');
+ System.assertEquals('5', testValueCaptured.s, 'the values inside the argument captured should be the same of the original one');
+ }
+
+ @isTest
+ static void thatCaptureArgumentOnlyFromVerifiedMethod()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Fred');
+ //the next call should be ignored because is not the method that has under verify,
+ //even if have the same type specified in the capturer.
+ mockList.addMore('Barney');
+
+ // Then
+
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+ ((fflib_MyList.IList) mocks.verify(mockList)).add((String) argument.capture());
+
+ System.assertEquals('Fred', (String)argument.getValue(), 'the argument captured is not as expected');
+ System.assertEquals(1, argument.getAllValues().size(), 'the argument captured should be only one');
+ }
+
+ @isTest
+ static void thatCaptureAllArgumentsForTheVerifiedMethods()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ List stringList = new List {'3'};
+ // When
+ mockList.add('Fred');
+ mockList.add(stringList);
+
+ mockList.clear();
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList)).add((String) argument.capture());
+ ((fflib_MyList.IList) mocks.verify(mockList)).add((List) argument.capture());
+
+ System.assertEquals(stringList, (List)argument.getValue(), 'the argument captured is not as expected');
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(2, argsCaptured.size(), 'expected 2 argument to be captured');
+
+ System.assertEquals('Fred', (String) argsCaptured[0], 'the first value is not as expected');
+ }
+
+ @isTest
+ static void thatCaptureArgumentFromRequestedParameter()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Fred', 'Barney', 'Wilma', 'Betty');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList))
+ .add(
+ (String) fflib_Match.eq('Fred'),
+ (String) fflib_Match.eq('Barney'),
+ (String) argument.capture(),
+ (String) fflib_Match.eq('Betty'));
+
+ System.assertEquals('Wilma', (String)argument.getValue(),
+ 'the argument captured is not as expected, should be Wilma because is the 3rd parameter in the call');
+ }
+
+ @isTest
+ static void thatCaptureLastArgument()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Barney');
+ mockList.add('Fred');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).add((String) argument.capture());
+
+ System.assertEquals('Fred', (String)argument.getValue(), 'the argument captured is not as expected');
+ }
+
+ @isTest
+ static void thatCaptureAllArguments()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Fred');
+ mockList.add('Barney');
+ mockList.add('Wilma');
+ mockList.add('Betty');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList, 4)).add((String) argument.capture());
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(4, argsCaptured.size(), 'expected 4 argument to be captured');
+
+ System.assertEquals('Fred', (String) argsCaptured[0], 'the first value is not as expected');
+ System.assertEquals('Barney', (String) argsCaptured[1], 'the second value is not as expected');
+ System.assertEquals('Wilma', (String) argsCaptured[2], 'the third value is not as expected');
+ System.assertEquals('Betty', (String) argsCaptured[3], 'the forth value is not as expected');
+ }
+
+ @isTest
+ static void thatCaptureAllArgumentsFromMultipleMethods()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Fred');
+ mockList.add('Barney');
+ mockList.get2(3, 'pebble');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList, 2)).add((String) argument.capture());
+
+ ((fflib_MyList.IList) mocks.verify(mockList))
+ .get2(
+ (Integer) fflib_Match.eq(3),
+ (String) argument.capture());
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(3, argsCaptured.size(), 'expected 3 argument to be captured');
+
+ System.assertEquals('Fred', (String) argsCaptured[0], 'the first value is not as expected');
+ System.assertEquals('Barney', (String) argsCaptured[1], 'the second value is not as expected');
+
+ System.assertEquals('pebble', (String) argsCaptured[2], 'the third value is not as expected');
+ }
+
+ @isTest
+ static void thatCanHandleMultipleCapturesInOneMethodCall()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Fred', 'Barney', 'Wilma', 'Betty');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList))
+ .add(
+ (String) fflib_Match.eq('Fred'),
+ (String) argument.capture(),
+ (String) argument.capture(),
+ (String) fflib_Match.eq('Betty'));
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(2, argsCaptured.size(), 'expected 2 argument to be captured');
+
+ System.assertEquals('Barney', (String) argsCaptured[0], 'the first value is not as expected');
+
+ System.assertEquals('Wilma', (String) argsCaptured[1], 'the second value is not as expected');
+ }
+
+ @isTest
+ static void thatDoesNotCaptureIfNotVerified()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('3');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(List.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList, fflib_ApexMocks.NEVER))
+ .add((List) argument.capture());
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(0, argsCaptured.size(), 'expected 0 argument to be captured');
+
+ System.assertEquals(null, argument.getValue(), 'no value should be captured, so must return null');
+ }
+
+ @isTest
+ static void thatCaptureOnlyMethodsThatMatchesWithOtherMatcherAsWell()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Same', 'Same', 'First call', 'First call');
+ mockList.add('Same', 'Same', 'Second call', 'Second call');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(
+ fflib_Match.eqString('Same'),
+ fflib_Match.eqString('Same'),
+ (String)argument.capture(),
+ fflib_Match.eqString('First call'));
+
+ System.assertEquals('First call', (String)argument.getValue());
+ }
+
+ @isTest
+ static void thatDoesNotCaptureAnythingWhenCaptorIsWrappedInAMatcher()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+
+ // When
+ mockList.add('Same', 'Same', 'First call', 'First call');
+ mockList.add('Same', 'Same', 'Second call', 'Second call');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) mocks.verify(mockList)).add(
+ (String) fflib_Match.allOf(
+ fflib_Match.eqString('Same'),
+ fflib_Match.eqString('Same'),
+ argument.capture()),
+ (String) fflib_Match.allOf(
+ fflib_Match.eqString('Same'),
+ fflib_Match.eqString('Same'),
+ argument.capture()),
+ (String) fflib_Match.allOf(
+ argument.capture(),
+ fflib_Match.eqString('First call')),
+ (String) fflib_Match.allOf(
+ argument.capture(),
+ fflib_Match.eqString('First call'))
+ );
+
+ List capturedValues = argument.getAllValues();
+
+ System.assertEquals(0, capturedValues.size(),
+ 'nothing should have been capture because the matcher it not really a capture type, but a allOf()');
+ System.assertEquals(null, (String)argument.getValue(),
+ 'nothing should have been capture because the matcher it not really a capture type, but a allOf()');
+ }
+
+ @isTest
+ static void thatArgumentValueIsCapturedWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+ // When
+ mockList.add('Fred');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList)inOrder1.verify(mockList, mocks.calls(1))).add((String) argument.capture());
+
+ System.assertEquals('Fred', (String)argument.getValue(), 'the argument captured is not as expected');
+ }
+
+ @isTest
+ static void thatCanPerformFurtherAssertionsOnCapturedArgumentValueWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ //When
+ TestInnerClass testValue = new TestInnerClass();
+ testValue.i = 4;
+ testValue.s = '5';
+
+ mockList.set(1, testValue);
+
+ //Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(TestInnerClass.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1))).set(fflib_Match.anyInteger(), argument.capture());
+
+ Object capturedArg = argument.getValue();
+ System.assertNotEquals(null, capturedArg, 'CapturedArg should not be null');
+
+ System.assert(capturedArg instanceof TestInnerClass, 'CapturedArg should be SObject, instead was ' + capturedArg);
+
+ TestInnerClass testValueCaptured = (TestInnerClass)capturedArg;
+
+ System.assertEquals(4, testValueCaptured.i, 'the values inside the argument captured should be the same of the original one');
+ System.assertEquals('5', testValueCaptured.s, 'the values inside the argument captured should be the same of the original one');
+ }
+
+ @isTest
+ static void thatCaptureArgumentOnlyFromVerifiedMethodWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('Fred');
+ //the next call should be ignored because is not the method that has under verify,
+ //even if have the same type specified in the capturer.
+ mockList.addMore('Barney');
+
+ // Then
+
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1))).add((String) argument.capture());
+
+ System.assertEquals('Fred', (String)argument.getValue(), 'the argument captured is not as expected');
+ System.assertEquals(1, argument.getAllValues().size(), 'the argument captured should be only one');
+ }
+
+ @isTest
+ static void thatCaptureAllArgumentsForTheVerifiedMethodsWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ List stringList = new List {'3'};
+ // When
+ mockList.add('Fred');
+ mockList.add(stringList);
+
+ mockList.clear();
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1))).add((String) argument.capture());
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1))).add((List) argument.capture());
+
+ System.assertEquals(stringList, (List)argument.getValue(), 'the argument captured is not as expected');
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(2, argsCaptured.size(), 'expected 2 argument to be captured');
+
+ System.assertEquals('Fred', (String) argsCaptured[0], 'the first value is not as expected');
+ }
+
+ @isTest
+ static void thatCaptureArgumentFromRequestedParameterWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('Fred', 'Barney', 'Wilma', 'Betty');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1)))
+ .add(
+ (String) fflib_Match.eq('Fred'),
+ (String) fflib_Match.eq('Barney'),
+ (String) argument.capture(),
+ (String) fflib_Match.eq('Betty'));
+
+ System.assertEquals('Wilma', (String)argument.getValue(),
+ 'the argument captured is not as expected, should be Wilma because is the 3rd parameter in the call');
+ }
+
+ @isTest
+ static void thatCaptureLastArgumentWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('Barney');
+ mockList.add('Fred');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(2))).add((String) argument.capture());
+
+ System.assertEquals('Fred', (String)argument.getValue(), 'the argument captured is not as expected');
+ }
+
+ @isTest
+ static void thatCaptureAllArgumentsWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('Fred');
+ mockList.add('Barney');
+ mockList.add('Wilma');
+ mockList.add('Betty');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(4))).add((String) argument.capture());
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(4, argsCaptured.size(), 'expected 4 argument to be captured');
+
+ System.assertEquals('Fred', (String) argsCaptured[0], 'the first value is not as expected');
+ System.assertEquals('Barney', (String) argsCaptured[1], 'the second value is not as expected');
+ System.assertEquals('Wilma', (String) argsCaptured[2], 'the third value is not as expected');
+ System.assertEquals('Betty', (String) argsCaptured[3], 'the forth value is not as expected');
+ }
+
+ @isTest
+ static void thatCaptureAllArgumentsFromMultipleMethodsWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('Fred');
+ mockList.add('Barney');
+ mockList.get2(3, 'pebble');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(2))).add((String) argument.capture());
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1)))
+ .get2(
+ (Integer) fflib_Match.eq(3),
+ (String) argument.capture());
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(3, argsCaptured.size(), 'expected 3 argument to be captured');
+
+ System.assertEquals('Fred', (String) argsCaptured[0], 'the first value is not as expected');
+ System.assertEquals('Barney', (String) argsCaptured[1], 'the second value is not as expected');
+
+ System.assertEquals('pebble', (String) argsCaptured[2], 'the third value is not as expected');
+ }
+
+ @isTest
+ static void thatCanHandleMultipleCapturesInOneMethodCallWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('Fred', 'Barney', 'Wilma', 'Betty');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1)))
+ .add(
+ (String) fflib_Match.eq('Fred'),
+ (String) argument.capture(),
+ (String) argument.capture(),
+ (String) fflib_Match.eq('Betty'));
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(2, argsCaptured.size(), 'expected 2 argument to be captured');
+
+ System.assertEquals('Barney', (String) argsCaptured[0], 'the first value is not as expected');
+
+ System.assertEquals('Wilma', (String) argsCaptured[1], 'the second value is not as expected');
+ }
+
+ @isTest
+ static void thatDoesNotCaptureIfNotVerifiedWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('3');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(List.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.never()))
+ .add((List) argument.capture());
+
+ List argsCaptured = argument.getAllValues();
+
+ System.assertEquals(0, argsCaptured.size(), 'expected 0 argument to be captured');
+
+ System.assertEquals(null, argument.getValue(), 'no value should be captured, so must return null');
+ }
+
+ @isTest
+ static void thatCaptureOnlyMethodsThatMatchesWithOtherMatcherAsWellWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('Same', 'Same', 'First call', 'First call');
+ mockList.add('Same', 'Same', 'Second call', 'Second call');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1))).add(
+ fflib_Match.eqString('Same'),
+ fflib_Match.eqString('Same'),
+ (String)argument.capture(),
+ fflib_Match.eqString('First call'));
+
+ System.assertEquals('First call', (String)argument.getValue());
+ }
+
+ @isTest
+ static void thatDoesNotCaptureAnythingWhenCaptorIsWrappedInAMatcherWithInOrderVerification()
+ {
+ // Given
+ fflib_ApexMocks mocks = new fflib_ApexMocks();
+ fflib_MyList mockList = (fflib_MyList)mocks.mock(fflib_MyList.class);
+ fflib_InOrder inOrder1 = new fflib_InOrder(mocks, new List{ mockList });
+
+
+ // When
+ mockList.add('Same', 'Same', 'First call', 'First call');
+ mockList.add('Same', 'Same', 'Second call', 'Second call');
+
+ // Then
+ fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(String.class);
+
+ ((fflib_MyList.IList) inOrder1.verify(mockList, mocks.calls(1))).add(
+ (String) fflib_Match.allOf(
+ fflib_Match.eqString('Same'),
+ fflib_Match.eqString('Same'),
+ argument.capture()),
+ (String) fflib_Match.allOf(
+ fflib_Match.eqString('Same'),
+ fflib_Match.eqString('Same'),
+ argument.capture()),
+ (String) fflib_Match.allOf(
+ argument.capture(),
+ fflib_Match.eqString('First call')),
+ (String) fflib_Match.allOf(
+ argument.capture(),
+ fflib_Match.eqString('First call'))
+ );
+
+ List