using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Akua.Framework.Infrastructure.Expressions; using Rhino.Mocks.Constraints; using Rhino.Mocks.Interfaces; namespace Akua.Framework.Specifications.Mocking { public delegate object ActionToUseToCreateMockForType(Type typeOnWhichToPerformAction, params object[] paramsToAssignToMock); public delegate IMethodOptions ActionToUseToSetupMockForType(object ignored); public class MethodChainMockerConfigurator { private IDictionary _MethodConstraints { get; set; } private ActionToUseToCreateMockForType _ActionToUseToCreateMockForType { get; set; } private ActionToUseToSetupMockForType _ActionToUseToSetupMockForType { get; set; } public MethodChainMockerConfigurator ChainUsing(ActionToUseToCreateMockForType actionToUseToCreateMockForType, ActionToUseToSetupMockForType actionToUseToSetupMockForType) { _ActionToUseToCreateMockForType = actionToUseToCreateMockForType; _ActionToUseToSetupMockForType = actionToUseToSetupMockForType; return this; } public MethodChainMockerConfigurator ConstrainChainAt(int atMethodOnIndex, params AbstractConstraint[] constraints) { if (_MethodConstraints == null) _MethodConstraints = new Dictionary(); _MethodConstraints.Add(atMethodOnIndex, constraints); return this; } public void On(AttachToT input, Expression> expressionToInspect) { (new MethodChainMocker(_ActionToUseToCreateMockForType, _ActionToUseToSetupMockForType)).MockOutChainFor(input, expressionToInspect, _MethodConstraints); } public void On(AttachToT input, LastMethodOutputT withValueToReturnAtTheEndOfChain, Expression> expressionToInspect) { (new MethodChainMocker(_ActionToUseToCreateMockForType, _ActionToUseToSetupMockForType)).MockOutChainFor(input, withValueToReturnAtTheEndOfChain, expressionToInspect, _MethodConstraints); } } public class MethodChainMocker { public MethodChainMocker(ActionToUseToCreateMockForType actionToUseToCreateMockForType, ActionToUseToSetupMockForType actionToUseToSetupMockForType) { _ActionToUseToCreateMockForType = actionToUseToCreateMockForType; _ActionToUseToSetupMockForType = actionToUseToSetupMockForType; } private ActionToUseToCreateMockForType _ActionToUseToCreateMockForType { get; set; } private ActionToUseToSetupMockForType _ActionToUseToSetupMockForType { get; set; } private InspectLambda _ { get { return new InspectLambda(); } } private IEnumerable _CollectCallableMethodCallsFrom(Expression> expressionToInspect) { return _.CollectMethodCallsFrom(expressionToInspect).Reverse(); } private IEnumerable _CollectCallableMethodCallsFrom(Expression> expressionToInspect) { return _.CollectMethodCallsFrom(expressionToInspect).Reverse(); } private void _MockMethodReturnTypes(IDictionary mocksDictionary, IEnumerable methods, ActionToUseToCreateMockForType actionToUseToCreateMock) { foreach (var methodInfo in methods) { if (methodInfo.Name == "Void") continue; if (mocksDictionary.ContainsKey(methodInfo.ReturnType)) continue; mocksDictionary.Add(methodInfo.ReturnType, actionToUseToCreateMock(methodInfo.ReturnType)); } } private IDictionary _CreateMockMethodReturnTypesAlongWithFirstDefaultMember(IEnumerable methods, FirstMemberT firstMemberInstance) { var mockedMethodReturnTypes = new Dictionary(); mockedMethodReturnTypes.Add(typeof(FirstMemberT), firstMemberInstance); _MockMethodReturnTypes(mockedMethodReturnTypes, methods, _ActionToUseToCreateMockForType); return mockedMethodReturnTypes; } private object[] _GetMethodParameters(MethodInfo fromMethod, IEnumerable methodCalls) { return (from callableMethod in methodCalls where callableMethod.Method == fromMethod select callableMethod.ParameterValues).Single(); } private object _GetMockByMethodReturnType(IDictionary mocksDictionary, MethodInfo methodFromWhichToFindMock) { return _GetMockByType(mocksDictionary, methodFromWhichToFindMock.ReturnType); } private object _GetMockByType(IDictionary mocksDictionary, Type type) { if (mocksDictionary.ContainsKey(type) == false) throw new InvalidOperationException("Cannot find mock for type '" + type.Name + "' !"); return mocksDictionary[type]; } private bool _IsLastMethodInChainByIndex(int methodChainIndex, MethodInfo[] methodChainMethods) { _VerifyMethodChainContainsAMethodOnIndex(methodChainMethods, methodChainIndex); return methodChainIndex == methodChainMethods.Length - 1; } private MethodInfo _GetMethodInfoByIndexFromChain(int methodChainIndex, MethodInfo[] methodChainMethods) { _VerifyMethodChainContainsAMethodOnIndex(methodChainMethods, methodChainIndex); return methodChainMethods[methodChainIndex]; } private object _ResolveMockedMethodReturnValue(int currentMethodChainIndex, MethodInfo[] methodChainMethods, IDictionary methodMocks, object lastMethodInChainReturnValue) { var method = _GetMethodInfoByIndexFromChain(currentMethodChainIndex, methodChainMethods); return (_IsLastMethodInChainByIndex(currentMethodChainIndex, methodChainMethods)) ? lastMethodInChainReturnValue : _GetMockByMethodReturnType(methodMocks, method); } private void _VerifyMethodChainContainsAMethodOnIndex(MethodInfo[] methodChainMethods, int methodChainIndex) { if ((methodChainMethods.Length - 1) < methodChainIndex) throw new IndexOutOfRangeException("Method chain does not contain a method on index " + methodChainIndex + "!"); } private MethodInfo[] _GetArrayOfMethodsOnly(IEnumerable methodCalls) { return _GetListOfMethodsOnly(methodCalls).ToArray(); } private IEnumerable _GetListOfMethodsOnly(IEnumerable methodCalls) { foreach (var methodCallPair in methodCalls) { yield return methodCallPair.Method; } } private Type _MethodInChainDeclaringType(MethodInfo fromMethod, IEnumerable methodCalls) { return (from callableMethod in methodCalls where callableMethod.Method == fromMethod select callableMethod.DeclaringType).Single(); } private AbstractConstraint[] _GetConstraintsByIndex(int index, object[] methodParameters, IDictionary methodsConstraints) { if (methodsConstraints == null || methodsConstraints.ContainsKey(index) == false) return _GetEmptyConstraintsForMethodParameters(methodParameters); return methodsConstraints[index]; } private AbstractConstraint[] _GetEmptyConstraintsForMethodParameters(object[] methodParameters) { if (methodParameters == null || methodParameters.Length == 0) return new List().ToArray(); var constraints = new List(); foreach (var methodParameter in methodParameters) { constraints.Add(Rhino.Mocks.Constraints.Is.Anything()); } return constraints.ToArray(); } public void MockOutChainFor(AttachToT input, Expression> expressionToInspect, IDictionary methodsConstraints) { var chainedCallableMethodCalls = _CollectCallableMethodCallsFrom(expressionToInspect); _MockOutChainFor(input, null, chainedCallableMethodCalls, methodsConstraints); } public void MockOutChainFor(AttachToT input, LastMethodOutputT withValueToReturnAtTheEndOfChain, Expression> expressionToInspect, IDictionary methodsConstraints) { var chainedCallableMethodCalls = _CollectCallableMethodCallsFrom(expressionToInspect); _MockOutChainFor(input, withValueToReturnAtTheEndOfChain, chainedCallableMethodCalls, methodsConstraints); } private void _MockOutChainFor(AttachToT input, object withValueToReturnAtTheEndOfChain, IEnumerable chainedCallableMethodCalls, IDictionary methodsConstraints) { var chainedMethodsList = _GetArrayOfMethodsOnly(chainedCallableMethodCalls); var chainedMethodMocks = _CreateMockMethodReturnTypesAlongWithFirstDefaultMember(chainedMethodsList, input); for (var i = 0; i < chainedMethodsList.Count(); i++) { var methodInChain = _GetMethodInfoByIndexFromChain(i, chainedMethodsList); var methodParameters = _GetMethodParameters(methodInChain, chainedCallableMethodCalls); var methodInChainDeclaringType = _MethodInChainDeclaringType(methodInChain, chainedCallableMethodCalls); var attachToMethodMock = _GetMockByType(chainedMethodMocks, methodInChainDeclaringType); var methodReturnValue = _ResolveMockedMethodReturnValue(i, chainedMethodsList, chainedMethodMocks, withValueToReturnAtTheEndOfChain); var methodConstraints = _GetConstraintsByIndex(i, methodParameters, methodsConstraints); if (methodReturnValue == null) _ActionToUseToSetupMockForType(methodInChain.Invoke(attachToMethodMock, methodParameters)) .Constraints(methodConstraints); else _ActionToUseToSetupMockForType(methodInChain.Invoke(attachToMethodMock, methodParameters)) .Return(methodReturnValue) .Constraints(methodConstraints); } } } }