Created
March 29, 2016 11:56
-
-
Save urasandesu/f3c84f4b7c7bbb4b632e to your computer and use it in GitHub Desktop.
Prototype: AutoFixture with auto mocking using Moq and Prig
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Moq; | |
using Moq.Language; | |
using NUnit.Framework; | |
using Ploeh.AutoFixture; | |
using Ploeh.AutoFixture.Kernel; | |
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Diagnostics; | |
using System.Diagnostics.Prig; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Prig; | |
using System.Reflection; | |
using Urasandesu.Prig.Framework; | |
namespace ClassLibrary1 | |
{ | |
public static class ULProcessMixin | |
{ | |
const int ErrorCancelled = 1223; | |
public static bool RestartCurrentProcess() | |
{ | |
return RestartCurrentProcessWith(null); | |
} | |
public static bool RestartCurrentProcessWith(Action<ProcessStartInfo> additionalSetup) | |
{ | |
var curProc = Process.GetCurrentProcess(); | |
var startInfo = curProc.StartInfo; | |
startInfo.UseShellExecute = true; | |
startInfo.WorkingDirectory = Environment.CurrentDirectory; | |
startInfo.FileName = curProc.MainModule.FileName; | |
var commandLineArgs = Environment.GetCommandLineArgs().Skip(1).ToArray(); | |
if (commandLineArgs.Any()) | |
startInfo.Arguments = "\"" + string.Join("\" \"", commandLineArgs.Select(_ => _.Replace("\"", "\\\"")).ToArray()) + "\""; | |
if (additionalSetup != null) | |
additionalSetup(startInfo); | |
try | |
{ | |
Process.Start(startInfo); | |
} | |
catch (Win32Exception ex) | |
{ | |
if (ex.NativeErrorCode == ErrorCancelled) | |
return false; | |
throw; | |
} | |
curProc.CloseMainWindow(); | |
return true; | |
} | |
} | |
[TestFixture] | |
public class ULProcessMixinTest | |
{ | |
[Test] | |
public void RestartCurrentProcess_should_return_false_if_user_cancelled_starting_process() | |
{ | |
using (new IndirectionsContext()) | |
{ | |
// Arrange | |
var ms = new MockStorage(MockBehavior.Strict); | |
var fixture = new Fixture().Customize(new MyCustomization(ms)); | |
fixture.Create<PEnvironment>(); | |
var curProcMainMod = fixture.Create<PProxyProcessModule>(); | |
var curProc = fixture.Create<PProxyProcess>(); | |
curProc.MainModuleGet().Body = @this => curProcMainMod; | |
fixture.Create<PProcess>(); | |
ms.Customize(m => m. | |
Do(PProcess.StartProcessStartInfo).Setup(_ => _(It.IsAny<ProcessStartInfo>())).Throws(new Win32Exception(1223)) | |
); | |
PProcess.GetCurrentProcess().Body = () => curProc; | |
// Act | |
var result = ULProcessMixin.RestartCurrentProcess(); | |
// Assert | |
Assert.IsFalse(result); | |
} | |
} | |
} | |
public class MockStorage : MockRepository | |
{ | |
Dictionary<Delegate, Mock> m_storage = new Dictionary<Delegate, Mock>(); | |
public MockStorage(MockBehavior defaultBehavior) | |
: base(defaultBehavior) | |
{ } | |
public void Assign(Delegate dlgt, Mock m) | |
{ | |
m_storage[dlgt] = m; | |
} | |
public void Assign<T>(Func<TypedBehaviorPreparable<T>> func, Mock<T> m) where T : class | |
{ | |
Assign((Delegate)func, (Mock)m); | |
} | |
public MockStorage Customize(Action<MockStorage> exp) | |
{ | |
exp(this); | |
return this; | |
} | |
public Mock<T> Do<T>(Func<TypedBehaviorPreparable<T>> func) where T : class | |
{ | |
return (Mock<T>)m_storage[func]; | |
} | |
} | |
public class MyCustomization : ICustomization | |
{ | |
readonly MockStorage m_ms; | |
public MyCustomization(MockStorage ms) | |
{ | |
m_ms = ms; | |
} | |
public void Customize(IFixture fixture) | |
{ | |
fixture.Customizations.Add(new IntPtrInitializer(new PrigTypeMocker(m_ms))); | |
} | |
} | |
public class IntPtrInitializer : ISpecimenBuilder | |
{ | |
private ISpecimenBuilder m_builder; | |
public IntPtrInitializer(ISpecimenBuilder builder) | |
{ | |
m_builder = builder; | |
} | |
public object Create(object request, ISpecimenContext context) | |
{ | |
if (context == null) | |
throw new ArgumentNullException("context"); | |
var result = m_builder.Create(request, context); | |
if (!(result is NoSpecimen)) | |
return result; | |
var requestedType = request as Type; | |
if (requestedType == null) | |
return new NoSpecimen(); | |
{ | |
if (requestedType == typeof(IntPtr)) | |
return IntPtr.Zero; | |
} | |
return new NoSpecimen(); | |
} | |
} | |
public class PrigTypeMocker : ISpecimenBuilder | |
{ | |
readonly MockStorage m_ms; | |
public PrigTypeMocker(MockStorage ms) | |
{ | |
m_ms = ms; | |
} | |
public object Create(object request, ISpecimenContext context) | |
{ | |
if (context == null) | |
throw new ArgumentNullException("context"); | |
var requestedType = request as Type; | |
if (requestedType == null) | |
return new NoSpecimen(); | |
{ | |
var result = CreatePrigType(requestedType, context); | |
if (!(result is NoSpecimen)) | |
return result; | |
} | |
{ | |
var result = CreateInstancePrigType(requestedType, context); | |
if (!(result is NoSpecimen)) | |
return result; | |
} | |
return new NoSpecimen(); | |
} | |
const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; | |
object CreatePrigType(Type type, ISpecimenContext context) | |
{ | |
var methods = type.GetMethods(PublicStatic).Where(_ => typeof(IBehaviorPreparable).IsAssignableFrom(_.ReturnType)).ToArray(); | |
if (methods.Length == 0) | |
return new NoSpecimen(); | |
foreach (var method in methods) | |
SetAutoBody(null, method, context); | |
return null; | |
} | |
const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance; | |
object CreateInstancePrigType(Type type, ISpecimenContext context) | |
{ | |
type = ReplaceInstancePrigType(type); | |
var methods = type.GetMethods(PublicInstance).Where(_ => typeof(IBehaviorPreparable).IsAssignableFrom(_.ReturnType)).ToArray(); | |
if (methods.Length == 0) | |
return new NoSpecimen(); | |
var obj = Activator.CreateInstance(type); | |
foreach (var method in methods) | |
SetAutoBody(obj, method, context); | |
return obj; | |
} | |
Type ReplaceInstancePrigType(Type type) | |
{ | |
var instPrigTypeName = string.Format("{0}.Prig.PProxy{1}", type.Namespace, type.Name); | |
return GetInstancePrigType(instPrigTypeName) ?? type; | |
} | |
static Dictionary<string, Type> ms_instPrigTypes; | |
static Dictionary<string, Type> InstancePrigTypes | |
{ | |
get | |
{ | |
if (ms_instPrigTypes == null) | |
{ | |
var repository = new IndirectionAssemblyRepository(); | |
var instPrigTypes = repository.FindAll().SelectMany(_ => _.GetTypes()).Where(_ => _.Name.StartsWith("PProxy")).ToDictionary(_ => _.FullName); | |
ms_instPrigTypes = new Dictionary<string, Type>(instPrigTypes); | |
} | |
return ms_instPrigTypes; | |
} | |
} | |
Type GetInstancePrigType(string fullName) | |
{ | |
var result = default(Type); | |
InstancePrigTypes.TryGetValue(fullName, out result); | |
return result; | |
} | |
void SetAutoBody(object obj, MethodInfo method, ISpecimenContext context) | |
{ | |
var preparable = method.Invoke(obj, null); | |
var preparableType = preparable.GetType(); | |
var bodyProp = preparableType.GetProperty("Body"); | |
var m = GetAutoMock(bodyProp.PropertyType, context); | |
bodyProp.SetValue(preparable, m.Object, null); | |
var funcType = typeof(Func<>); | |
var funcTypeInst = funcType.MakeGenericType(method.ReturnType); | |
m_ms.Assign(Delegate.CreateDelegate(funcTypeInst, obj, method), m); | |
} | |
Mock GetAutoMock(Type dlgtType, ISpecimenContext context) | |
{ | |
Debug.Assert(dlgtType.IsSubclassOf(typeof(Delegate))); | |
var invokeMethod = dlgtType.GetMethod("Invoke"); | |
var createMethod = m_ms.GetType().GetMethod("Create", Type.EmptyTypes); | |
var createMethodInst = createMethod.MakeGenericMethod(dlgtType); | |
var m = (Mock)createMethodInst.Invoke(m_ms, null); | |
DoSetupForAutoMock(m, invokeMethod, context); | |
return m; | |
} | |
void DoSetupForAutoMock(Mock m, MethodInfo invokeMethod, ISpecimenContext context) | |
{ | |
var mockType = m.GetType(); | |
var setupMethod = GetSetupMethod(mockType, invokeMethod); | |
var expression = GetExpression(setupMethod.GetParameters()[0].ParameterType, invokeMethod); | |
var setup = setupMethod.Invoke(m, new object[] { expression }); | |
DoReturnsForAutoMock(setup, context); | |
} | |
MethodInfo GetSetupMethod(Type mockType, MethodInfo invokeMethod) | |
{ | |
var setupMethods = mockType.GetMethods(PublicInstance).Where(_ => _.Name == "Setup"); | |
if (invokeMethod.ReturnType == typeof(void)) | |
return GetActionSetupMethod(setupMethods); | |
else | |
return GetFuncSetupMethod(setupMethods, invokeMethod.ReturnType); | |
} | |
MethodInfo GetActionSetupMethod(IEnumerable<MethodInfo> setupMethods) | |
{ | |
return setupMethods.First(_ => _.GetParameters().ContainsInGenericArguments(typeof(Action<>))); | |
} | |
MethodInfo GetFuncSetupMethod(IEnumerable<MethodInfo> setupMethods, Type resultType) | |
{ | |
return setupMethods.First(_ => _.GetParameters().ContainsInGenericArguments(typeof(Func<,>))).MakeGenericMethod(resultType); | |
} | |
Expression GetExpression(Type setupParamType, MethodInfo invokeMethod) | |
{ | |
var lambdaMethod = typeof(Expression).GetMethods(PublicStatic). | |
Where(_ => _.Name == "Lambda"). | |
Where(_ => _.IsGenericMethod). | |
Where(_ => _.GetParameters().Length == 2). | |
Single(_ => _.GetParameters()[1].ParameterType.IsArray); | |
var lambdaType = setupParamType.GetGenericArguments()[0]; | |
var dlgtType = lambdaType.GetGenericArguments()[0]; | |
var lambdaMethodInst = lambdaMethod.MakeGenericMethod(lambdaType); | |
var paramExp = Expression.Parameter(dlgtType, "_"); | |
var anyAccedptableParamExps = GetAnyAccedptableParameterExpressions(invokeMethod); | |
var bodyExp = Expression.Invoke(paramExp, anyAccedptableParamExps); | |
var paramExps = new[] { paramExp }; | |
return (Expression)lambdaMethodInst.Invoke(null, new object[] { bodyExp, paramExps }); | |
} | |
class ValueHolder<T> | |
{ | |
public T Value; | |
} | |
IEnumerable<Expression> GetAnyAccedptableParameterExpressions(MethodInfo invokeMethod) | |
{ | |
return invokeMethod.GetParameters().Select(ToAnyAccedptableParameterExpression); | |
} | |
Expression ToAnyAccedptableParameterExpression(ParameterInfo paramInfo) | |
{ | |
var paramType = paramInfo.ParameterType; | |
if (paramType.IsByRef) | |
return ToAnyAccedptableRefParameterExpression(paramType); | |
else | |
return ToAnyAccedptableParameterExpression(paramType); | |
} | |
Expression ToAnyAccedptableRefParameterExpression(Type type) | |
{ | |
var valHolderType = typeof(ValueHolder<>).MakeGenericType(type); | |
var valHolder = Activator.CreateInstance(valHolderType); | |
var valField = valHolderType.GetField("Value"); | |
return Expression.Field(Expression.Constant(valHolder), valField); | |
} | |
Expression ToAnyAccedptableParameterExpression(Type type) | |
{ | |
var isAnyMethod = typeof(It).GetMethod("IsAny"); | |
var isAnyMethodInst = isAnyMethod.MakeGenericMethod(type); | |
return Expression.Call(null, isAnyMethodInst); | |
} | |
void DoReturnsForAutoMock(object setup, ISpecimenContext context) | |
{ | |
var setupType = setup.GetType(); | |
if (!setupType.GetInterfaces().Where(_ => _.IsGenericType).Any(_ => _.GetGenericTypeDefinition() == typeof(IReturns<,>))) | |
return; | |
var returnsMethod = setupType.GetMethods(PublicInstance).Single(ReturnsImmediateValue); | |
var result = context.Resolve(setupType.GetGenericArguments()[1]); | |
if (result is NoSpecimen) | |
return; | |
var resultType = result.GetType(); | |
var paramType = returnsMethod.GetParameters()[0].ParameterType; | |
if (!paramType.IsAssignableFrom(resultType)) | |
{ | |
var opImplicitMethod = resultType.GetMethods(PublicStatic). | |
Where(_ => _.Name == "op_Implicit"). | |
SingleOrDefault(_ => _.ReturnType == paramType); | |
if (opImplicitMethod == null) | |
throw new InvalidOperationException(string.Format("{0} doesn't have implicit conversion to {1}.", resultType, paramType)); | |
result = opImplicitMethod.Invoke(null, new object[] { result }); | |
} | |
returnsMethod.Invoke(setup, new object[] { result }); | |
} | |
bool ReturnsImmediateValue(MethodInfo method) | |
{ | |
if (method.Name != "Returns") | |
return false; | |
return !method.GetParameters().Any(_ => typeof(Delegate).IsAssignableFrom(_.ParameterType)); | |
} | |
} | |
static class MyEnumerable | |
{ | |
public static bool ContainsInGenericArguments(this IEnumerable<ParameterInfo> paramInfos, Type typeDefinition) | |
{ | |
return paramInfos.Any(_ => _.ContainsInGenericArguments(typeDefinition)); | |
} | |
public static bool ContainsInGenericArguments(this ParameterInfo paramInfo, Type typeDefinition) | |
{ | |
return paramInfo.ParameterType.ContainsInGenericArguments(typeDefinition); | |
} | |
public static bool ContainsInGenericArguments(this Type t, Type typeDefinition) | |
{ | |
if (typeDefinition.IsGenericType && !typeDefinition.IsGenericTypeDefinition) | |
throw new ArgumentException("The parameter must be a type definition.", "typeDefinition"); | |
return t.EnumerateGenericArgument().Contains(typeDefinition); | |
} | |
public static IEnumerable<Type> EnumerateGenericArgument(this Type t) | |
{ | |
if (t.IsGenericType) | |
yield return t.GetGenericTypeDefinition(); | |
else | |
yield return t; | |
foreach (var genericArg in t.GetGenericArguments()) | |
foreach (var chidGenericArg in genericArg.EnumerateGenericArgument()) | |
yield return chidGenericArg; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment