Skip to content

Instantly share code, notes, and snippets.

@enkelmedia
Last active September 17, 2019 14:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save enkelmedia/5e7c242d08d8af6b7b3149af2e952ab2 to your computer and use it in GitHub Desktop.
Save enkelmedia/5e7c242d08d8af6b7b3149af2e952ab2 to your computer and use it in GitHub Desktop.
ModelsBuilderMock.cs
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using Moq;
using Umbraco.Core.Models;
using Umbraco.ModelsBuilder;
namespace Obviuse.Tests
{
/*
* Inspired by: https://our.umbraco.com/forum/using-umbraco-and-getting-started/81058-unit-testing-moq-mocking-for-umbraco-models-builder-object
*
*/
/// <summary>
/// Use this class to Mock any item which is built by the Umbraco Models Builder. This is required
/// because by default the Models Builder item calls several extension methods of the IPublishedProperty & IPublishedContent.
/// This makes life way easy.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ModelsBuilderMock<T> where T : IPublishedContent
{
private readonly Mock<IPublishedContent> _mockedContent;
public T Mock { get; }
public Mock<IPublishedContent> MockedContent { get { return _mockedContent; } }
public ModelsBuilderMock()
{
_mockedContent = new Mock<IPublishedContent>();
Mock = CreateObjectWithAnyConstructor<T>(_mockedContent.Object);
_mockedContent.Setup(x => x.DocumentTypeAlias).Returns(typeof(T).UnderlyingSystemType.GetField("ModelTypeAlias", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null).ToString());
}
public void SetProperty<TResult>(Expression<Func<T, TResult>> expression, TResult value)
{
var propertyRes = GetExpressionText(expression);
if (propertyRes.IsGenericProperty)
{
//The Models Builder actual umbraco Alias are in the formate of, for e.g. 'metaRobots', or 'age'.
var mockedProperty = new Mock<IPublishedProperty>();
//This is because the extension method GetPropertyValue from the PublishedContentExtensions
_mockedContent.Setup(x => x.GetProperty(propertyRes.PropertyName, false)).Returns(mockedProperty.Object);
_mockedContent.Setup(x => x.GetProperty(propertyRes.PropertyName)).Returns(mockedProperty.Object);
mockedProperty.SetupAllProperties();
mockedProperty.SetupGet(x => x.Value).Returns(value);
mockedProperty.SetupGet(x => x.HasValue).Returns(true);
}
else
{
var convertedExpression = ToUntypedPropertyExpression<IPublishedContent, TResult>(expression, propertyRes.PropertyName);
this.MockedContent.Setup<TResult>(convertedExpression).Returns((TResult)value);
}
}
private static Expression<Func<TRet, TResult>> ToUntypedPropertyExpression<TRet, TResult>(Expression<Func<T, TResult>> expression, string memberName)
{
var param = Expression.Parameter(typeof(TRet));
var field = Expression.Property(param, memberName);
return Expression.Lambda<Func<TRet, TResult>>(field, param);
}
private static GetPropertyResult GetExpressionText<TModel, TRes>(Expression<Func<TModel, TRes>> selector)
{
var propName = GetPropertyName(selector);
try
{
var pInfo = typeof(TModel).GetProperty(propName).GetCustomAttribute<ImplementPropertyTypeAttribute>();
return new GetPropertyResult()
{
IsGenericProperty = true,
PropertyName = pInfo.Alias
};
}
catch (Exception e)
{
return new GetPropertyResult()
{
IsGenericProperty = false,
PropertyName = propName
};
}
}
private static string GetPropertyName<TModel, TRes>(Expression<Func<TModel, TRes>> expression)
{
var currentExpression = expression.Body;
while (true)
{
switch (currentExpression.NodeType)
{
case ExpressionType.Parameter:
return ((ParameterExpression)currentExpression).Name;
case ExpressionType.MemberAccess:
return ((MemberExpression)currentExpression).Member.Name;
case ExpressionType.Call:
return ((MethodCallExpression)currentExpression).Method.Name;
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
currentExpression = ((UnaryExpression)currentExpression).Operand;
break;
case ExpressionType.Invoke:
currentExpression = ((InvocationExpression)currentExpression).Expression;
break;
case ExpressionType.ArrayLength:
return "Length";
default:
throw new Exception("Not a proper member selector");
}
}
}
//Ideally place these static methods in a separate file
private static T CreateObjectWithAnyConstructor<TMod>(params object[] parameters)
{
return (T)CreateObjectWithAnyConstructor(typeof(TMod), parameters);
}
private static object CreateObjectWithAnyConstructor(Type typeToCreate, params object[] parameters)
{
ConstructorInfo constructor = null;
try
{
// Binding flags exclude public constructors.
constructor =
typeToCreate.GetConstructors()
.Where(x => x.GetParameters().Length == parameters.Length)
.FirstOrDefault();
//constructor = typeToCreate.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null);
}
catch (Exception)
{
throw;
}
if (constructor == null || constructor.IsAssembly)
// Also exclude internal constructors.
throw new InvalidOperationException(string.Format("A private or " + "protected constructor is missing for '{0}'.", typeToCreate.Name));
object _instance = null;
try
{
_instance = constructor.Invoke(parameters);
}
catch (MemberAccessException ex)
{
var m = Regex.Match(ex.Message, "Cannot create an instance of (.*?)Factory because it is an abstract class.");
if (m.Success)
{
string factoryName = m.Groups[1].Value + "Factory";
string msg = ex.Message +
" - If you are using this factory, make sure you mark it as 'UsedInProject' in the builder";
throw new InvalidOperationException(msg);
}
else
{
throw;
}
}
catch (Exception)
{
throw;
}
return _instance;
}
private class GetPropertyResult
{
public bool IsGenericProperty { get; set; }
public string PropertyName { get; set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment