We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider to include to Moq code base a function to generate mock from anonymous object. I wrote the following working prototype.
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Diagnostics; using System.Runtime.CompilerServices; namespace Tests { using PropertyValue = KeyValuePair<string,object>; /// <summary> /// Builds <see cref="Mock<T>"/> from anonymous object. /// </summary> internal class AnonyMock { /// <summary> /// Builds mock for given type from anonymous object. /// </summary> /// <typeparam name="T">The interface to mock.</typeparam> /// <param name="obj">Anonymous object.</param> /// <returns></returns> public static T Of<T>(object obj) where T:class { Debug.Assert(IsAnon(obj)); return Mock.Of(MakePredicate<T>(obj)); } private static Expression<Func<T, bool>> MakePredicate<T>(object obj) { var type = typeof(T); var parameter = Expression.Parameter(typeof(T), "t"); var props = ( from p in obj.GetType().GetProperties() where p.Name != "type" select PropertyPredicate(parameter, obj, type, p) ).ToArray(); return Expression.Lambda<Func<T, bool>>(props.Skip(1).Aggregate(props[0], Expression.AndAlso), parameter); } private static Expression PropertyPredicate(Expression parameter, object instance, Type targetType, PropertyInfo pi) { var value = pi.GetValue(instance, null); // example of composite properties Func<object, IEnumerable<PropertyValue>> destruct; if (CompositeProperties.TryGetValue(pi.Name, out destruct)) { var props = (from p in destruct(value) select PropertyEqual(parameter, targetType, p.Key, p.Value)).ToArray(); return props.Skip(1).Aggregate(props[0], Expression.AndAlso); } var targetProperty = ResolveProperty(targetType, pi.Name); Debug.Assert(targetProperty != null); // support T[], IEnumerable<T>, IItemCollection<T> var array = value as Array; if (array != null) { // replace anonymous objects var itemType = GetItemType(targetProperty.PropertyType); var items = (from it in array.Cast<object>() select MockIfAnon(it, itemType)).ToArray(); var targetArray = Array.CreateInstance(itemType, items.Length); for (var i = 0; i < items.Length; i++) { targetArray.SetValue(items[i], i); } value = targetArray; if (targetProperty.PropertyType.GetGenericTypeDefinition() == typeof(IItemCollection<>)) { value = CreateItemCollection(targetArray, itemType); } } return Expression.Equal(Expression.Property(parameter, targetProperty), Expression.Constant(value)); } private static object MockIfAnon(object obj, Type type) { if (!IsAnon(obj)) return obj; var typeProperty = obj.GetType().GetProperty("type"); if (typeProperty != null) { type = (Type) typeProperty.GetValue(obj, null); } var method = typeof(Amock).GetMethod("Of").MakeGenericMethod(type); return method.Invoke(null, new[] { obj }); } private static Expression PropertyEqual(Expression target, Type declType, string name, object value) { var prop = GetProperty(target, declType, name); return Expression.Equal(prop, Expression.Constant(value)); } private static Expression GetProperty(Expression target, Type declType, string name) { return Expression.Property(target, ResolveProperty(declType, name)); } private static PropertyInfo ResolveProperty(Type type, string name) { // TODO cache properties var types = new[] {type}.Concat(BaseTypes(type)).Concat(type.GetInterfaces()).ToArray(); return types.Select(t => t.GetProperty(name)).FirstOrDefault(x => x != null); } private static IEnumerable<Type> BaseTypes(Type type) { while (type != null && type.BaseType != null) { yield return type.BaseType; type = type.BaseType; } } private static bool IsAnon(object value) { return value != null && CheckIfAnonymousType(value.GetType()); } private static bool CheckIfAnonymousType(Type type) { if (type == null) throw new ArgumentNullException("type"); // HACK: The only way to detect anonymous types right now. return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) && type.IsGenericType && type.Name.Contains("AnonymousType") && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; } private static Type GetItemType(Type type) { if (type == null) return null; if (type.IsArray) return type.GetElementType(); // IEnumerable<T> var i = FindIEnumerableT(type); if (i != null) return i.GetGenericArguments()[0]; // support non-generic collections like old .NET 1 collections based on CollectionBase if (!typeof(IEnumerable).IsAssignableFrom(type)) return null; var add = type.GetMethod("Add"); if (add == null) return null; var parameters = add.GetParameters(); if (parameters.Length != 1) return null; return parameters[0].ParameterType; } private static Type FindIEnumerableT(Type type) { if (type == null || type == typeof(string)) return null; if (type.IsArray) return typeof(IEnumerable<>).MakeGenericType(type.GetElementType()); if (type.IsGenericType) { var ienum = type.GetGenericArguments() .Select(x => typeof(IEnumerable<>).MakeGenericType(x)) .FirstOrDefault(x => x.IsAssignableFrom(type)); if (ienum != null) { return ienum; } } var ifaces = type.GetInterfaces(); if (ifaces.Length > 0) { var ienum = ifaces.Select(x => FindIEnumerableT(x)).FirstOrDefault(x => x != null); if (ienum != null) { return ienum; } } if (type.BaseType != null && type.BaseType != typeof(object)) { return FindIEnumerableT(type.BaseType); } return null; } private static object CreateItemCollection(Array items, Type itemType) { var method = typeof(Amock).GetMethod("ItemCollection").MakeGenericMethod(itemType); return method.Invoke(null, new[] {items}); } public static IItemCollection<T> ItemCollection<T>(params T[] items) { return items.ToItemCollection(); } } }
namespace Tests { [TestFixture] public class AnonyMockTests { [Test] public void TextBox() { var box = AnonyMock.Of<ITextBox>(new { Name = "a", Size = "1in,0.5in" }); Assert.AreEqual("a", box.Name); Assert.AreEqual((Length)"1in", box.Width); Assert.AreEqual((Length)"0.5in", box.Height); } } }
The text was updated successfully, but these errors were encountered:
@sergeyt, I like it: it would be useful to see it implemented directly in Moq. Why you don't submit a PR for this?
Sorry, something went wrong.
var box = AnonyMock.Of<ITextBox>(new { Name = "a", Size = "1in,0.5in" });
I am not sure I fully understand the intended meaning of this. Would it be equivalent to the following?:
var box = Mock.Of<ITextBox>(b => b.Name == "a" && b.Size = "1in,0.5in");
Is anyone still interested in this new feature?
No branches or pull requests
Consider to include to Moq code base a function to generate mock from anonymous object. I wrote the following working prototype.
Usage
The text was updated successfully, but these errors were encountered: