Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Simon 692 posts 1068 karma points
    Nov 02, 2016 @ 15:05
    Simon
    0

    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

  • Simon 692 posts 1068 karma points
    Nov 02, 2016 @ 20:29
    Simon
    0

    Any idea guys?

    Thank you

  • Tim Watts 90 posts 395 karma points
    Jan 31, 2017 @ 08:31
    Tim Watts
    0

    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:

    var value = content.GetPropertyValue("myAlias");
    

    This doesn't:

    var value = content.GetPropertyValue<string>("myAlias");
    

    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

  • Mark Cassar 5 posts 81 karma points
    Mar 21, 2017 @ 15:59
    Mark Cassar
    6

    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);
    

    Hope this helps! :)

  • Rahul Patel 8 posts 80 karma points
    Aug 25, 2017 @ 21:40
    Rahul Patel
    0

    This was very helpful, thanks so much!

  • Sotiris Filippidis 286 posts 1501 karma points
    Aug 16, 2018 @ 20:54
    Sotiris Filippidis
    0

    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:

    _mockedContent.Setup(x => x.DocumentTypeAlias).Returns(typeof(T).UnderlyingSystemType.GetField("ModelTypeAlias", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null).ToString());
    

    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.

  • Sotiris Filippidis 286 posts 1501 karma points
    Aug 16, 2018 @ 23:06
    Sotiris Filippidis
    0

    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:

    string propertyName = ExpressionHelper.GetExpressionText(propertyExpression);
    

    with this:

    string propertyName = GetExpressionText(propertyExpression);
    

    Code taken from https://gist.github.com/rmariuzzo/4716555

  • Hassan Mourad 5 posts 75 karma points
    Oct 15, 2018 @ 16:23
    Hassan Mourad
    0

    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 ?

  • Julien Kulker 75 posts 427 karma points c-trib
    Feb 11, 2019 @ 12:51
    Julien Kulker
    0

    Same problem

  • Markus Johansson 1911 posts 5756 karma points MVP c-trib
    Apr 18, 2019 @ 09:20
    Markus Johansson
    0

    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'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.

  • javafun 17 posts 50 karma points c-trib
    Feb 04, 2019 @ 19:56
    javafun
    0

    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

  • Markus Johansson 1911 posts 5756 karma points MVP c-trib
    Apr 17, 2019 @ 15:02
    Markus Johansson
    0

    I changed that one to:

    propertyName = propertyName.ToFirstLower();
    

    The ToFirstLower()-extension is in the Umbraco.Core-namespace, so also add:

    using Umbraco.Core;
    
  • javafun 17 posts 50 karma points c-trib
    Feb 06, 2019 @ 02:34
    javafun
    0

    Any hints would be very appreciated!

  • Markus Johansson 1911 posts 5756 karma points MVP c-trib
    Apr 17, 2019 @ 15:04
    Markus Johansson
    0

    I love this! Thanks Mark!

  • Markus Johansson 1911 posts 5756 karma points MVP c-trib
    Apr 18, 2019 @ 08:58
    Markus Johansson
    1

    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:

    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:

    _mockedContent.Setup(x => x.GetProperty(propertyName, false)).Returns(mockedProperty.Object);
    _mockedContent.Setup(x => x.GetProperty(propertyName)).Returns(mockedProperty.Object);
    

    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: Screenshot from Models Builder documentation

    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

  • w11stop.com/ arduino 1 post 20 karma points
    Apr 18, 2019 @ 12:03
    w11stop.com/ arduino
    0

    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.

  • Sotiris Filippidis 286 posts 1501 karma points
    Sep 17, 2019 @ 14:22
    Sotiris Filippidis
    0

    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:

      mockedProperty.SetupGet(x => x.Value).Returns(value);
      mockedProperty.SetupGet(x => x.HasValue).Returns(true);
    

    After:

    mockedProperty.Setup(x => x.GetValue(null,null)).Returns(value);
    mockedProperty.Setup(x => x.HasValue(null,null)).Returns(true);
    

    https://gist.github.com/sotirisf/70637838b03644a6d4079466f93141c4

  • Neel Kachhla 1 post 71 karma points
    Mar 13, 2023 @ 14:03
    Neel Kachhla
    0

    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

Please Sign in or register to post replies

Write your reply to:

Draft