Unit Testing - Moq mocking for Umbraco Models Builder Object
Hi Guys,
Does anyone know how I can mock, using moq, an umbraco models builder object?
I have searched about it and they say that properties should be vritual in order to be able to use SetUpGet and they are not virual and there are no setters.
I would like to mock my umbraco models builder object in order to be able to compare.
We have found a solution by using the following class. In case it helps anybody. See below for example of code:
/// <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 Mock<IPublishedContent> _mockedContent;
public T Mock { get; }
public Mock<IPublishedContent> MockedContent { get; }
public ModelsBuilderMock()
{
_mockedContent = new Mock<IPublishedContent>();
Mock = CreateObjectWithAnyConstructor<T>(_mockedContent.Object);
}
/// <summary>
/// Mock a property and its value.
/// </summary>
/// <param name="propertyExpression"></param>
/// <param name="value"></param>
public void SetProperty(Expression<Func<T, object>> propertyExpression, object value)
{
string propertyName = ExpressionHelper.GetExpressionText(propertyExpression);
//The Models Builder actual umbraco Alias are in the formate of, for e.g. 'metaRobots', or 'age'.
propertyName = TextHelpers.SetFirstLetterLowercase(propertyName);
var mockedProperty = new Mock<IPublishedProperty>();
//This is because the extension method GetPropertyValue from the PublishedContentExtensions
_mockedContent.Setup(x => x.GetProperty(propertyName, false)).Returns(mockedProperty.Object);
mockedProperty.SetupAllProperties();
mockedProperty.SetupGet(x => x.Value).Returns(value);
mockedProperty.SetupGet(x => x.HasValue).Returns(true);
}
//Ideally place these static methods in a separate file
public static T CreateObjectWithAnyConstructor<T>(params object[] parameters)
{
return (T)CreateObjectWithAnyConstructor(typeof(T), parameters);
}
public 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;
}
}
This is the example of how to use it then. FakeModelsBuilderData is a Models Builder generated class.
var basePageMock = new ModelsBuilderMock<FakeModelsBuilderData>();
var galleryValue = "aaaaaaaaa";
basePageMock.SetProperty(x => x.Gallery, galleryValue);
basePageMock.SetProperty(x => x.MetaRobots, "bbbbb");
var metaRobotsValue = "ccc";
basePageMock.SetProperty(x => x.MetaRobots, metaRobotsValue);
Assert.AreEqual(basePageMock.Mock.Gallery, galleryValue);
Assert.AreEqual(basePageMock.Mock.MetaRobots, metaRobotsValue);
This was indeed an extremely helpful piece of code. The only issue I had was that it wouldn't set the DocumentTypeAlias property so tests would fail if it was needed in code. After some digging, I added this line of code in the constructor:
And now my mocks come with the document type alias correctly set. I'm pretty sure that the bindingflags are redundant in the above code, I've just left them for completeness. This is a trick to get values from a type's constants using reflection, which I just learned about :) Hope it's useful.
One more thing I discovered was that ExpressionHelper sometimes returned an empty string instead of the property's name. After searching a bit, I found that this is expected, and somebody put together some code to fix that. So here it is:
public static string GetExpressionText<TModel>(Expression<Func<TModel, object>> expression)
{
var expr = (LambdaExpression)expression;
if (expr.Body.NodeType == ExpressionType.Convert)
{
var ue = expr.Body as UnaryExpression;
var me = ((ue != null) ? ue.Operand : null) as MemberExpression;
return me.Member.Name;
}
return ExpressionHelper.GetExpressionText(expr);
}
I use this as a static method inside the ModelsBuilderMock class, and swapped the following line in the SetProperty method:
i found this solution pretty awesome , and i am using it. but i have a problem , i cant seem able to set properties like "id - sortorder - name" , or in general any system property
do u have any idea why ? or how to accomplish this ?
I has the same issue, the SetProperty()-method assumes that you are trying to set a value on a property that is generated by Models Builder, in it's current state the code does not work with properties that are "standard properties", BUT you can set them using the underlaying moq-object:
var canonicalMockParent = new ModelsBuilderMock<ClassModel>();
canonicalMockParent.MockedContent.Setup(x => x.Url).Returns("https");
var canonicalMock = new ModelsBuilderMock<ClassModel>();
canonicalMock.MockedContent.Setup(x => x.Parent).Returns(canonicalMockParent.Mock);
Assert.AreEqual("https", canonicalMock.Mock.Parent.Url);
This required a small change in the ModelsBuilderMock-class
public Mock<IPublishedContent> MockedContent { get { return _mockedContent; } }
I'll see if I can find the time to figure out if we could extend the SetProperty()-method to work with both generated properties and properties from the base class.
Update, I've implemeted support for both generic properties and "standard properties" in the gist above.
I'm using the code that Mark Cassar posted, great stuff! I figured I'll share some improvements to the code here.
I had an issue when I wanted to reference another IPublishedContent that was accessed as a property on a mocked models builder class..
Example:
var canonicalMock = new ModelsBuilderMock<ClassModel>();
canonicalMock.SetProperty(x=>x.GitHubFolder,"GithubFolder1");
var startPageMock = new ModelsBuilderMock<StartModel>();
startPageMock.SetProperty(x=>x.CanonicalNode, canonicalMock.Mock);
Assert.AreSame(canonicalMock.Mock, startPageMock.Mock.CanonicalNode);
This test failed, since startPageMock.Mock.CanonicalNode was null. Turns out that I had to add a row to the SetProperty()-method in the ModelsBuilderMock-class:
The last row configures the GetProperty-method used without a parameter. After this my test works just fine and the canonicalMock is returned when calling startPageMock.Mock.CanonicalNode.
I was also inspired by the fixes that Sotiris Filippidis posted, but I took this one more step since the property name on the generated ModelsBuilder class might not always match with the actual alias of the property on the Document Type.
This means that the property on the class could be "Age" but the alias on the document type might be "customerAge" so getting the alias value from the ImplementPropertyType-attribute is a more safe way to figure out the property alias.
I've added the logic to fetch the attribute values and my fix for the "GetProperty"-method in this Gist on Github:
Firstly, thank you to you all for creating and perfecting the ModelsBuilderMock class. It has been very useful in mocking modelsbuilder objects for my unit tests.
I am now in the process of upgrading my project to Umbraco 8 and found that I am getting errors as some of the generated classes are now showing as IPublishedElement instead of IPublishedContent. Has anyone come across this issue? Would the best thing be to create a version of this ModelsBuilderMock class ( e.g. ModelsBuilderMockElement) that has type IPublishedElement class?
Unit Testing - Moq mocking for Umbraco Models Builder Object
Hi Guys,
Does anyone know how I can mock, using moq, an umbraco models builder object?
I have searched about it and they say that properties should be vritual in order to be able to use
SetUpGet
and they are not virual and there are no setters.I would like to mock my umbraco models builder object in order to be able to compare.
Thank you for your help.
Kind Regards
Any idea guys?
Thank you
Did you get anywhere with this Simon? I'm having the same issue now.
ModelsBuilder uses extension methods, which can't be used with Moq.
Whilst this works:
This doesn't:
And unfortunately it's the second method which is used by ModelsBuilder.
Apologies I haven't got a solution to add, but I'm hoping you've already found one which you can share!
Tim
We have found a solution by using the following class. In case it helps anybody. See below for example of code:
This is the example of how to use it then. FakeModelsBuilderData is a Models Builder generated class.
Hope this helps! :)
This was very helpful, thanks so much!
This was indeed an extremely helpful piece of code. The only issue I had was that it wouldn't set the DocumentTypeAlias property so tests would fail if it was needed in code. After some digging, I added this line of code in the constructor:
And now my mocks come with the document type alias correctly set. I'm pretty sure that the bindingflags are redundant in the above code, I've just left them for completeness. This is a trick to get values from a type's constants using reflection, which I just learned about :) Hope it's useful.
One more thing I discovered was that ExpressionHelper sometimes returned an empty string instead of the property's name. After searching a bit, I found that this is expected, and somebody put together some code to fix that. So here it is:
I use this as a static method inside the ModelsBuilderMock class, and swapped the following line in the SetProperty method:
with this:
Code taken from https://gist.github.com/rmariuzzo/4716555
i found this solution pretty awesome , and i am using it. but i have a problem , i cant seem able to set properties like "id - sortorder - name" , or in general any system property
do u have any idea why ? or how to accomplish this ?
Same problem
I has the same issue, the SetProperty()-method assumes that you are trying to set a value on a property that is generated by Models Builder, in it's current state the code does not work with properties that are "standard properties", BUT you can set them using the underlaying moq-object:
This required a small change in the ModelsBuilderMock-class
I've uploaded my version of the ModelsBuilderMock-class here: https://gist.github.com/enkelmedia/5e7c242d08d8af6b7b3149af2e952ab2
I'll see if I can find the time to figure out if we could extend the SetProperty()-method to work with both generated properties and properties from the base class.
Update, I've implemeted support for both generic properties and "standard properties" in the gist above.
I'm getting compilation error and it seems
SetFirstLetterLowercase
method doesn't seem available on TestHelper class.Can someone point me out where to find this method?
Thanks, V
I changed that one to:
The ToFirstLower()-extension is in the Umbraco.Core-namespace, so also add:
Any hints would be very appreciated!
I love this! Thanks Mark!
Hallo!
I'm using the code that Mark Cassar posted, great stuff! I figured I'll share some improvements to the code here.
I had an issue when I wanted to reference another IPublishedContent that was accessed as a property on a mocked models builder class..
Example:
This test failed, since startPageMock.Mock.CanonicalNode was null. Turns out that I had to add a row to the SetProperty()-method in the ModelsBuilderMock-class:
The last row configures the GetProperty-method used without a parameter. After this my test works just fine and the canonicalMock is returned when calling startPageMock.Mock.CanonicalNode.
I was also inspired by the fixes that Sotiris Filippidis posted, but I took this one more step since the property name on the generated ModelsBuilder class might not always match with the actual alias of the property on the Document Type.
See this documentation: https://our.umbraco.com/Documentation/reference/templating/Modelsbuilder/Control-Generation#implement-property-type
Screenshot from docs here:
This means that the property on the class could be "Age" but the alias on the document type might be "customerAge" so getting the alias value from the ImplementPropertyType-attribute is a more safe way to figure out the property alias.
I've added the logic to fetch the attribute values and my fix for the "GetProperty"-method in this Gist on Github:
https://gist.github.com/enkelmedia/5e7c242d08d8af6b7b3149af2e952ab2
I have also used the following code and I faced no problem. I guess this is one of the best solution for this.
Thanks for correcting this Mark.
Hello from me too,
I've created a gist based on Markus' gist that will work with Umbraco V8. Actually, those two lines have been changed:
Before:
After:
https://gist.github.com/sotirisf/70637838b03644a6d4079466f93141c4
Hi all,
Firstly, thank you to you all for creating and perfecting the ModelsBuilderMock class. It has been very useful in mocking modelsbuilder objects for my unit tests.
I am now in the process of upgrading my project to Umbraco 8 and found that I am getting errors as some of the generated classes are now showing as IPublishedElement instead of IPublishedContent. Has anyone come across this issue? Would the best thing be to create a version of this ModelsBuilderMock class ( e.g. ModelsBuilderMockElement) that has type IPublishedElement class?
Thanks in advance, Neel
is working on a reply...