diff --git a/config/project-scratch-def.json b/config/project-scratch-def.json index 541caf4..2a1bf21 100644 --- a/config/project-scratch-def.json +++ b/config/project-scratch-def.json @@ -6,11 +6,6 @@ "lightningExperienceSettings": { "enableS1DesktopEnabled": true }, - "securitySettings": { - "passwordPolicies": { - "enableSetPasswordInApi": true - } - }, "mobileSettings": { "enableS1EncryptedStoragePref2": false } diff --git a/force-app/main/default/classes/UniversalMocker.cls b/force-app/main/default/classes/UniversalMocker.cls index e4d68c8..369220e 100755 --- a/force-app/main/default/classes/UniversalMocker.cls +++ b/force-app/main/default/classes/UniversalMocker.cls @@ -1,292 +1,352 @@ -/************************************************************ - -*** @author: Suraj Pillai -*** @group: Test Class -*** @date: 01/2020 -*** @description: A universal class for mocking in tests. Contains a method for setting the return value for any method. Another method returns the number of times a method was called - -*/ -@isTest -public with sharing class UniversalMocker implements System.StubProvider { - private final Map>> argumentsMap = new Map>>(); - private final Type mockedClass; - private final Map mocksMap = new Map(); - private final Map callCountsMap = new Map(); - - private Boolean isInSetupMode = false; - private Boolean isInAssertMode = false; - private Boolean isInGetArgumentMode = false; - - private String currentMethodName; - private String currentParamTypesString; - private Integer expectedCallCount; - private Integer forInvocationNumber = 0; - - private String INVALID_STATE_ERROR_MSG = 'Mocker object state is invalid for this operation. Please refer to the Readme'; - private String KEY_DELIMITER = '||'; - - //Map for storing mutators - Map> mutatorMap = new Map>(); - - public enum Times { - OR_LESS, - OR_MORE, - EXACTLY - } - public static UniversalMocker mock(Type mockedClass) { - return new UniversalMocker(mockedClass); - } - - public Object createStub() { - return Test.createStub(this.mockedClass, this); - } - public UniversalMocker when(String stubbedMethodName) { - if (this.isAnyModeActive()) { - throw new InvalidOperationException(INVALID_STATE_ERROR_MSG); - } - this.isInSetupMode = true; - this.currentMethodName = stubbedMethodName; - return this; - } - - public UniversalMocker withParamTypes(List paramTypes) { - if (!this.isAnyModeActive()) { - throw new InvalidOperationException('Invalid order of operations. Must specify method name to mock/assert first'); - } - this.currentParamTypesString = this.getParamTypesString(paramTypes); - return this; - } - - public UniversalMocker mutateWith(Mutator mutatorInstance) { - if (!this.isInSetupMode) { - throw new InvalidOperationException('Invalid order of operations. Must specify method name to mock/assert first'); - } - String key = this.getCurrentKey(); - if (this.mutatorMap.containsKey(key)) { - this.mutatorMap.get(key).add(mutatorInstance); - } else { - this.mutatorMap.put(key, new List{ mutatorInstance }); - } - if (!this.callCountsMap.containsKey(key)) { - this.callCountsMap.put(key, 0); - } - return this; - } - - public UniversalMocker thenReturnVoid() { - return this.thenReturn(null); - } - - public UniversalMocker thenReturn(Object returnObject) { - if (!this.isInSetupMode) { - throw new InvalidOperationException('Invalid order of operations. Must specify method name to mock/assert first'); - } - String key = this.getCurrentKey(); - this.mocksMap.put(key, returnObject); - if (!this.callCountsMap.containsKey(key)) { - this.callCountsMap.put(key, 0); - } - this.resetState(); - return this; - } - - public UniversalMocker thenThrow(Exception exceptionToThrow) { - return this.thenReturn(exceptionToThrow); - } - public Object handleMethodCall( - Object stubbedObject, - String stubbedMethodName, - Type returnType, //currently unused - List listOfParamTypes, - List listOfParamNames, - List listOfArgs - ) { - if (this.isAnyModeActive()) { - throw new InvalidOperationException(INVALID_STATE_ERROR_MSG); - } - String keyInUse = this.determineKeyToUseForCurrentStubbedMethod(stubbedMethodName, listOfParamTypes); - this.incrementCallCount(keyInUse); - this.saveArguments(listOfParamNames, listOfArgs, keyInUse); - - Object returnValue = this.mocksMap.get(keyInUse); - - if (this.mutatorMap.containsKey(keyInUse)) { - for (Mutator m : this.mutatorMap.get(keyInUse)) { - m.mutate(stubbedObject, stubbedMethodName, listOfParamTypes, listOfArgs); - } - } - - if (returnValue instanceof Exception) { - throw (Exception) returnValue; - } - return returnValue; - } - - public UniversalMocker assertThat() { - if (this.isAnyModeActive()) { - throw new InvalidOperationException(INVALID_STATE_ERROR_MSG); - } - this.isInAssertMode = true; - return this; - } - - public UniversalMocker method(String methodName) { - if (!this.isInAssertMode) { - throw new InvalidOperationException('Invalid order of operations. Method called without calling assertThat first'); - } - this.currentMethodName = methodName; - return this; - } - - public void wasCalled(Integer expectedCallCount) { - wasCalled(expectedCallCount, UniversalMocker.Times.EXACTLY); - } - - public void wasCalled(Integer expectedCallCount, Times assertTypeValue) { - if (!this.isInAssertMode) { - throw new InvalidOperationException('Invalid order of operations. Method called without calling assertThat first'); - } - this.expectedCallCount = expectedCallCount; - String currentKey = this.getCurrentKey(); - Integer actualCallCount = this.callCountsMap.get(currentKey); - String methodName = this.currentMethodName; - this.resetState(); - switch on assertTypeValue { - when OR_LESS { - system.assert(this.expectedCallCount >= actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'less than or equal')); - } - when OR_MORE { - system.assert(this.expectedCallCount <= actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'more than or equal')); - } - when else { - system.assertEquals(this.expectedCallCount, actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'equal')); - } - } - } - - public void wasNeverCalled() { - if (!this.isInAssertMode) { - throw new InvalidOperationException('Invalid order of operations. Method called without calling assertThat first'); - } - String currentKey = this.getCurrentKey(); - Integer actualCallCount = this.callCountsMap.get(currentKey); - String methodName = this.currentMethodName; - this.resetState(); - if (actualCallCount != null) { - this.expectedCallCount = 0; - system.assertEquals(this.expectedCallCount, actualCallCount, String.format('Method {0} was called 1 or more times', new List{ methodName })); - } - } - - public UniversalMocker forMethod(String stubbedMethodName) { - if (this.isAnyModeActive()) { - throw new InvalidOperationException(INVALID_STATE_ERROR_MSG); - } - this.isInGetArgumentMode = true; - this.currentMethodName = stubbedMethodName; - return this; - } - - public UniversalMocker andInvocationNumber(Integer invocation) { - if (!this.isInGetArgumentMode) { - throw new InvalidOperationException('Invalid order of operations. Method called without calling \'forMethod\' first'); - } - this.forInvocationNumber = invocation; - return this; - } - - public Object getValueOf(String paramName) { - if (!this.isInGetArgumentMode) { - throw new InvalidOperationException('Invalid order of operations. Method called without calling \'forMethod\' first'); - } - String theKey = this.getCurrentKey(); - Map paramsMap = argumentsMap.get(theKey).get(this.forInvocationNumber); - if (!paramsMap.containsKey(paramName.toLowerCase())) { - throw new IllegalArgumentException(String.format('Param name {0} not found for the method {1}', new List{ paramName, this.currentMethodName })); - } - Object returnValue = paramsMap.get(paramName.toLowerCase()); - this.resetState(); - return returnValue; - } - - public Object getArgumentsMap() { - if (!this.isInGetArgumentMode) { - throw new InvalidOperationException('Invalid order of operations. Method called without calling \'forMethod\' first'); - } - String theKey = this.getCurrentKey(); - Map returnValue = this.argumentsMap.get(theKey).get(this.forInvocationNumber); - this.resetState(); - return returnValue; - } - - public class InvalidOperationException extends Exception { - } - - public interface Mutator { - void mutate(Object stubbedObject, String stubbedMethodName, List listOfParamTypes, List listOfArgs); - } - - private UniversalMocker(Type mockedClass) { - this.mockedClass = mockedClass; - } - - private String getCurrentKey() { - String retVal = this.currentMethodName; - if (this.currentParamTypesString != null) { - retVal += KEY_DELIMITER + this.currentParamTypesString; - } - return retVal.toLowerCase(); - } - - private String getKey(String methodName, List paramTypes) { - return (methodName + KEY_DELIMITER + this.getParamTypesString(paramTypes)).toLowerCase(); - } - - private String getParamTypesString(List paramTypes) { - String[] classNames = new List{}; - for (Type paramType : paramTypes) { - classNames.add(paramType.getName()); - } - return String.join(classNames, '-'); - } - - private void resetState() { - this.currentParamTypesString = null; - this.currentMethodName = null; - this.isInAssertMode = false; - this.isInSetupMode = false; - this.isInGetArgumentMode = false; - this.forInvocationNumber = 0; - } - - private boolean isAnyModeActive() { - return this.isInSetupMode || this.isInAssertMode || this.isInGetArgumentMode; - } - - private String determineKeyToUseForCurrentStubbedMethod(String stubbedMethodName, List listOfParamTypes) { - String keyWithParamTypes = this.getKey(stubbedMethodName, listOfParamTypes); - return this.callCountsMap.containsKey(keyWithParamTypes) ? keyWithParamTypes : stubbedMethodName.toLowerCase(); - } - - private void incrementCallCount(String key) { - Integer count = this.callCountsMap.containsKey(key) ? this.callCountsMap.get(key) : 0; - this.callCountsMap.put(key, count + 1); - } - - private void saveArguments(List listOfParamNames, List listOfArgs, String key) { - Map currentArgsMap = new Map(); - if (!this.argumentsMap.containsKey(key)) { - this.argumentsMap.put(key, new List>{ currentArgsMap }); - } else { - this.argumentsMap.get(key).add(currentArgsMap); - } - - for (Integer i = 0; i < listOfParamNames.size(); i++) { - currentArgsMap.put(listOfParamNames[i].toLowerCase(), listOfArgs[i]); - } - } - - private String getMethodCallCountAssertMessage(String methodName, String comparison) { - return String.format('Expected call count for method {0} is not {1} to the actual count', new List{ methodName, comparison }); - } -} +/************************************************************ + +*** @author: Suraj Pillai +*** @group: Test Class +*** @date: 01/2020 +*** @description: A universal class for mocking in tests. Contains a method for setting the return value for any method. Another method returns the number of times a method was called. https://github.com/surajp/universalmock + +*/ +@isTest +public with sharing class UniversalMocker implements System.StubProvider { + private final Map>> argumentsMap = new Map>>(); + private final Type mockedClass; + private final Map mocksMap = new Map(); + private final Map callCountsMap = new Map(); + + private String currentMethodName; + private String currentParamTypesString; + private Integer expectedCallCount; + private Integer forInvocationNumber = 0; + + private String KEY_DELIMITER = '||'; + + //Map for storing mutators + Map> mutatorMap = new Map>(); + + // Inner class instances + private SetupMode_Entry setupAInstance; + private AssertMode_Entry assertAInstance; + private AssertMode_Midway assertBInstance; + private GetParamsMode_Entry getParamsAInstance; + + private enum Modes { + SETUP, + ASSERT, + GETPARAMS + } + + /* Begin Public Methods */ + + public enum Times { + OR_LESS, + OR_MORE, + EXACTLY + } + public static UniversalMocker mock(Type mockedClass) { + return new UniversalMocker(mockedClass); + } + + public Object createStub() { + return Test.createStub(this.mockedClass, this); + } + + public class SetupMode_Entry extends SetupMode_Midway { + private SetupMode_Entry(UniversalMocker parent) { + super(parent); + } + public SetupMode_Midway withParamTypes(List paramTypes) { + this.parent.withParamTypes(paramTypes); + return (SetupMode_Midway) this; + } + } + + public virtual class SetupMode_Midway { + private final UniversalMocker parent; + private SetupMode_Midway(UniversalMocker parent) { + this.parent = parent; + } + public void thenReturnVoid() { + this.parent.thenReturnVoid(); + } + public void thenReturn(Object returnObject) { + this.parent.thenReturn(returnObject); + } + public SetupMode_Midway mutateWith(Mutator mutatorInstance) { + this.parent.mutateWith(mutatorInstance); + return this; + } + public void thenThrow(Exception exceptionToThrow) { + this.parent.thenThrow(exceptionToThrow); + } + } + + public class AssertMode_Entry { + private final UniversalMocker parent; + private AssertMode_Entry(UniversalMocker parent) { + this.parent = parent; + } + public AssertMode_Midway method(String methodName) { + parent.method(methodName); + return parent.assertBInstance; + } + } + + public class AssertMode_Midway extends AssertMode_Exit { + private AssertMode_Midway(UniversalMocker parent) { + super(parent); + } + public AssertMode_Exit withParamTypes(List paramTypes) { + parent.withParamTypes(paramTypes); + return (AssertMode_Exit) this; + } + } + + public virtual class AssertMode_Exit { + private final UniversalMocker parent; + private AssertMode_Exit(UniversalMocker parent) { + this.parent = parent; + } + public void wasCalled(Integer expectedCallCount, Times assertTypeValue) { + parent.wasCalled(expectedCallCount, assertTypeValue); + } + public void wasCalled(Integer expectedCallCount) { + parent.wasCalled(expectedCallCount); + } + public void wasNeverCalled() { + parent.wasNeverCalled(); + } + } + + public class GetParamsMode_Entry extends GetParamsMode_Midway { + private GetParamsMode_Entry(UniversalMocker parent) { + super(parent); + } + public GetParamsMode_Midway withParamTypes(List paramTypes) { + parent.withParamTypes(paramTypes); + return (GetParamsMode_Midway) this; + } + } + + public virtual class GetParamsMode_Midway extends GetParamsMode_Exit { + private GetParamsMode_Midway(UniversalMocker parent) { + super(parent); + } + public GetParamsMode_Exit andInvocationNumber(Integer invocation) { + parent.andInvocationNumber(invocation); + return (GetParamsMode_Exit) this; + } + } + + public virtual class GetParamsMode_Exit { + private final UniversalMocker parent; + private GetParamsMode_Exit(UniversalMocker parent) { + this.parent = parent; + } + public Object getValueOf(String paramName) { + return parent.getValueOf(paramName); + } + public Map getArgumentsMap() { + return parent.getArgumentsMap(); + } + } + + public SetupMode_Entry when(String stubbedMethodName) { + this.currentMethodName = stubbedMethodName; + return this.setupAInstance; + } + + public Object handleMethodCall( + Object stubbedObject, + String stubbedMethodName, + Type returnType, //currently unused + List listOfParamTypes, + List listOfParamNames, + List listOfArgs + ) { + String keyInUse = this.determineKeyToUseForCurrentStubbedMethod(stubbedMethodName, listOfParamTypes); + this.incrementCallCount(keyInUse); + this.saveArguments(listOfParamNames, listOfArgs, keyInUse); + + Object returnValue = this.mocksMap.get(keyInUse); + + if (this.mutatorMap.containsKey(keyInUse)) { + for (Mutator m : this.mutatorMap.get(keyInUse)) { + m.mutate(stubbedObject, stubbedMethodName, listOfParamTypes, listOfArgs); + } + } + + if (returnValue instanceof Exception) { + throw (Exception) returnValue; + } + return returnValue; + } + + public AssertMode_Entry assertThat() { + return this.assertAInstance; + } + + public GetParamsMode_Entry forMethod(String stubbedMethodName) { + this.currentMethodName = stubbedMethodName; + return this.getParamsAInstance; + } + + public class InvalidOperationException extends Exception { + } + + public interface Mutator { + void mutate(Object stubbedObject, String stubbedMethodName, List listOfParamTypes, List listOfArgs); + } + + /* End Public methods */ + + /* Begin Private methods */ + + private void withParamTypes(List paramTypes) { + this.currentParamTypesString = this.getParamTypesString(paramTypes); + } + + private void mutateWith(Mutator mutatorInstance) { + String key = this.getCurrentKey(); + if (this.mutatorMap.containsKey(key)) { + this.mutatorMap.get(key).add(mutatorInstance); + } else { + this.mutatorMap.put(key, new List{ mutatorInstance }); + } + if (!this.callCountsMap.containsKey(key)) { + this.callCountsMap.put(key, 0); + } + } + + private void thenReturnVoid() { + this.thenReturn(null); + } + + private void thenReturn(Object returnObject) { + String key = this.getCurrentKey(); + this.mocksMap.put(key, returnObject); + if (!this.callCountsMap.containsKey(key)) { + this.callCountsMap.put(key, 0); + } + } + + private void thenThrow(Exception exceptionToThrow) { + this.thenReturn(exceptionToThrow); + } + + private void method(String methodName) { + this.currentMethodName = methodName; + } + + private void wasCalled(Integer expectedCallCount) { + wasCalled(expectedCallCount, UniversalMocker.Times.EXACTLY); + } + + private void wasCalled(Integer expectedCallCount, Times assertTypeValue) { + this.expectedCallCount = expectedCallCount; + String currentKey = this.getCurrentKey(); + Integer actualCallCount = this.callCountsMap.get(currentKey); + String methodName = this.currentMethodName; + switch on assertTypeValue { + when OR_LESS { + system.assert(this.expectedCallCount >= actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'less than or equal')); + } + when OR_MORE { + system.assert(this.expectedCallCount <= actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'more than or equal')); + } + when else { + system.assertEquals(this.expectedCallCount, actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'equal')); + } + } + } + + private void wasNeverCalled() { + String currentKey = this.getCurrentKey(); + Integer actualCallCount = this.callCountsMap.get(currentKey); + String methodName = this.currentMethodName; + if (actualCallCount != null) { + this.expectedCallCount = 0; + system.assertEquals(this.expectedCallCount, actualCallCount, String.format('Method {0} was called 1 or more times', new List{ methodName })); + } + } + + private void andInvocationNumber(Integer invocation) { + this.forInvocationNumber = invocation; + } + + private Object getValueOf(String paramName) { + String theKey = this.getCurrentKey(); + Map paramsMap = argumentsMap.get(theKey).get(this.forInvocationNumber); + if (!paramsMap.containsKey(paramName.toLowerCase())) { + throw new IllegalArgumentException(String.format('Param name {0} not found for the method {1}', new List{ paramName, this.currentMethodName })); + } + Object returnValue = paramsMap.get(paramName.toLowerCase()); + return returnValue; + } + + private Map getArgumentsMap() { + String theKey = this.getCurrentKey(); + Map returnValue = this.argumentsMap.get(theKey).get(this.forInvocationNumber); + return returnValue; + } + + private String getCurrentKey() { + String retVal = this.currentMethodName; + if (this.currentParamTypesString != null) { + retVal += KEY_DELIMITER + this.currentParamTypesString; + } + return retVal.toLowerCase(); + } + + private String getKey(String methodName, List paramTypes) { + return (methodName + KEY_DELIMITER + this.getParamTypesString(paramTypes)).toLowerCase(); + } + + private String getParamTypesString(List paramTypes) { + String[] classNames = new List{}; + for (Type paramType : paramTypes) { + classNames.add(paramType.getName()); + } + return String.join(classNames, '-'); + } + + private String determineKeyToUseForCurrentStubbedMethod(String stubbedMethodName, List listOfParamTypes) { + String keyWithParamTypes = this.getKey(stubbedMethodName, listOfParamTypes); + return this.callCountsMap.containsKey(keyWithParamTypes) ? keyWithParamTypes : stubbedMethodName.toLowerCase(); + } + + private void incrementCallCount(String key) { + Integer count = this.callCountsMap.containsKey(key) ? this.callCountsMap.get(key) : 0; + this.callCountsMap.put(key, count + 1); + } + + private void saveArguments(List listOfParamNames, List listOfArgs, String key) { + Map currentArgsMap = new Map(); + if (!this.argumentsMap.containsKey(key)) { + this.argumentsMap.put(key, new List>{ currentArgsMap }); + } else { + this.argumentsMap.get(key).add(currentArgsMap); + } + + for (Integer i = 0; i < listOfParamNames.size(); i++) { + currentArgsMap.put(listOfParamNames[i].toLowerCase(), listOfArgs[i]); + } + } + + private String getMethodCallCountAssertMessage(String methodName, String comparison) { + return String.format('Expected call count for method {0} is not {1} to the actual count', new List{ methodName, comparison }); + } + + private UniversalMocker(Type mockedClass) { + this.mockedClass = mockedClass; + this.initInnerClassInstances(); + } + + private void initInnerClassInstances() { + this.setupAInstance = new SetupMode_Entry(this); + this.assertAInstance = new AssertMode_Entry(this); + this.assertBInstance = new AssertMode_Midway(this); + this.getParamsAInstance = new GetParamsMode_Entry(this); + } + + /* End Private Methods */ +} diff --git a/force-app/main/default/classes/UniversalMocker.cls-meta.xml b/force-app/main/default/classes/UniversalMocker.cls-meta.xml index bb01112..871a8cf 100755 --- a/force-app/main/default/classes/UniversalMocker.cls-meta.xml +++ b/force-app/main/default/classes/UniversalMocker.cls-meta.xml @@ -1,5 +1,5 @@ - - - 49.0 - Active - + + + 53.0 + Active + diff --git a/force-app/main/default/classes/example/AccountDomainTest.cls b/force-app/main/default/classes/example/AccountDomainTest.cls index c921cca..fdcfdb0 100644 --- a/force-app/main/default/classes/example/AccountDomainTest.cls +++ b/force-app/main/default/classes/example/AccountDomainTest.cls @@ -1,22 +1,21 @@ @isTest public with sharing class AccountDomainTest { - private static final UniversalMocker mockService; private static final AccountDBService mockServiceStub; private static final AccountDomain sut; // system under test - + static { mockService = UniversalMocker.mock(AccountDBService.class); mockServiceStub = (AccountDBService) mockService.createStub(); sut = new AccountDomain(mockServiceStub); } - + @isTest public static void it_should_return_one_account() { //setup String mockedMethodName = 'getOneAccount'; Account mockAccount = new Account(Name = 'Mock Account'); - + mockService.when(mockedMethodName).thenReturn(mockAccount); //test @@ -91,10 +90,10 @@ public with sharing class AccountDomainTest { //verify mockService.assertThat().method(mockedMethodName).withParamTypes(new List{ Id.class }).wasCalled(1); mockService.assertThat().method(mockedMethodName).withParamTypes(new List{ String.class }).wasCalled(1); - Id accountIdParam = (Id) mockService.forMethod(mockedMethodName).andInvocationNumber(0).withParamTypes(new List{ Id.class }).getValueOf('accountId'); + Id accountIdParam = (Id) mockService.forMethod(mockedMethodName).withParamTypes(new List{ Id.class }).andInvocationNumber(0).getValueOf('accountId'); String acctNameParam = (String) mockService.forMethod(mockedMethodName) - .andInvocationNumber(0) .withParamTypes(new List{ String.class }) + .andInvocationNumber(0) .getValueOf('accountName'); System.assertEquals(mockAccountId, accountIdParam); diff --git a/sfdx-project.json b/sfdx-project.json index 985f821..4b2f656 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -7,5 +7,5 @@ ], "namespace": "", "sfdcLoginUrl": "https://login.salesforce.com", - "sourceApiVersion": "49.0" + "sourceApiVersion": "53.0" }