Copied to clipboard

Flag this post as spam?

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


  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 18, 2019 @ 15:46
    Dennis Adolfi
    0

    Unit Testing v8

    Hi. Are there any documentation of how to properly mock dependencies for unit testing Umbraco v8?

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 18, 2019 @ 18:21
    Dennis Adolfi
    0

    For instance: mocking the dependencies needed for a RenderMvcController or a SurfaceController in v8?

  • Matthew Wise 191 posts 799 karma points c-trib
    Mar 18, 2019 @ 18:28
    Matthew Wise
    0

    I don't know of any documentation, but you could checkout the umbraco until tests

  • Alexandre Locas 18 posts 99 karma points
    Mar 18, 2019 @ 18:34
    Alexandre Locas
    0

    I am also very interested in finding a way to do this.

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 20, 2019 @ 14:17
    Dennis Adolfi
    1

    Anyone?

    I added a request for the Umbraco Documentation on how to get started with Unit Testing in Umbraco 8: https://github.com/umbraco/UmbracoDocs/issues/1627

  • Simon Andrews 13 posts 106 karma points
    Mar 20, 2019 @ 15:47
    Simon Andrews
    0

    So at the moment you just need to clone the Umbraco.CMS and use the built in tests as a guide, it's a bit of a pain but once you've got a few tests working it gets easier to know what to do.

    Just for an example, I made a custom UrlProvider that I needed to test and here's a version of one of the tests I wrote using XUnit and ExpectedObjects:

        [Theory]
        [InlineData("/xxxx/zzzz/yyyy/")]
        [InlineData("/xxxx/bbbb/cccc")]
        public void GetUrl_PassedContent_ReturnsValidUrl(string parentUrl)
        {
            var urlProvider = new MyUrlProvider(_moqRequestSettings.Object, _moqGlobalSettings.Object, _moqLogger.Object);
    
            var currentUri = new Uri("https://www.google.com");
    
            var mockPropertyOne = new Mock<IPublishedProperty>();
            mockPropertyOne.Setup(x => x.Alias).Returns("mockPropertyOne");
            mockPropertyOne.Setup(x => x.GetValue(It.IsAny<string>(), It.IsAny<string>())).Returns("Property One");
            mockPropertyOne.Setup(x => x.GetSourceValue(It.IsAny<string>(), It.IsAny<string>())).Returns("Property One");
    
            var mockPropertyTwo = new Mock<IPublishedProperty>();
            mockPropertyTwo.Setup(x => x.Alias).Returns("mockPropertyTwo");
            mockPropertyTwo.Setup(x => x.GetValue(It.IsAny<string>(), It.IsAny<string>())).Returns("Property Two");
            mockPropertyTwo.Setup(x => x.GetSourceValue(It.IsAny<string>(), It.IsAny<string>())).Returns("Property Two");
    
            var parent = new Mock<IPublishedContent>();
            parent.Setup(x => x.ContentType).Returns(new PublishedContentType(1234, "BasicContentPage", PublishedItemType.Content,
                                                                  Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
    
            parent.Setup(x => x.Url).Returns(parentUrl);
    
            var content = new Mock<IPublishedContent>();
            content.Setup(x => x.Parent).Returns(parent.Object);
    
            content.Setup(x => x.ContentType).Returns(new PublishedContentType(1234, "SpecificPropertyPage", PublishedItemType.Content,
                                                                   Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
    
            content.Setup(x => x.GetProperty(It.Is<string>(s => s.Equals("mockPropertyOne")))).Returns(mockPropertyOne.Object);
            content.Setup(x => x.GetProperty(It.Is<string>(s => s.Equals("mockPropertyTwo")))).Returns(mockPropertyTwo.Object);
    
            var result = urlProvider.GetUrl(null, content.Object, UrlProviderMode.Auto, "en-GB", currentUri);
            var expectedResult = new UrlInfo("https://www.google.com/xxxx/property-one-and-property-two", true, "en-GB").ToExpectedObject();
    
            expectedResult.ShouldEqual(result);
        }
    

    Hope this helps get you started.

    Thanks Simon Andrews

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 20, 2019 @ 17:00
    Dennis Adolfi
    0

    Thanks Simon. And how does your controller tests look like? Could you show some examples of those?

  • Simon Andrews 13 posts 106 karma points
    Mar 21, 2019 @ 10:11
    Simon Andrews
    0

    Hi Dennis,

    Unfortunately I've not managed to crack the controller testing yet, the IFactory that is in UmbracoApiController (the one I'm currently using) is causing issues as it needs some setup, I'm trying to find away to add a mocked version in my spare time but not managed it yet.

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 21, 2019 @ 10:34
    Dennis Adolfi
    102

    Same issue here, I figured it out however.

    So the issue I had was that when I tried to test a controller i got an error saying:

    System.InvalidOperationException : No factory has been set. at Umbraco.Core.Composing.Current.get_Factory()
    

    This was the same error you mentioned. This error is new to me since, I never got it in my Umbraco 7.x setup. So i searched the UmbracoCms and found a way for mocking the IFactory so that it worked for me.

    [SetUp]
        public void SetUp() {
            Current.Factory = Substitute.For<IFactory>();
        }
    
        [TearDown]
        public void TearDown() {
            Current.Reset();
        }
    

    (Im using NSubstitute for mocking here but I guess you can use Moq just as well.)

    The teardown is ecpessially important, otherwise your first test will run fine, but the second one will trow an error saying something like "a Factory has already been set".

    Hope this will help you as well, it worked for me. :) Cheers!

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 21, 2019 @ 10:37
    Dennis Adolfi
    1

    I still however think it would be great if there where a "Basic testing setup" for Umbraco v8 in the documentation so that anyone interested in starting to unit test Umbraco can quickly get started.

  • Simon Andrews 13 posts 106 karma points
    Mar 22, 2019 @ 10:51
    Simon Andrews
    2

    Firstly Thanks Dennis the Factory mocking worked nicely.

    Example of what I used:

    public class MyControllerTests
    {
        private readonly Mock<IFactory> _factory;
    
        public MyControllerTests()
        {
            _factory = new Mock<IFactory>();
        }
    
        [Fact]
        public void MyController_Test1)
        {
            Current.Factory = _factory.Object;
    
            var controller = new MyController();
    
            var result = controller.MyMethod();
    
            new MyResults().ToExpectedObject().ShouldEqual(result);
        }
    }
    

    Secondly, agreed it would be nice if there was a Umbraco Testing Help section to the documentation.

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 27, 2019 @ 07:05
    Dennis Adolfi
    3

    And in the spirit of sharing, here is what I implemented.

    The home controller/viewmodel implementation are based on the Route Hijacking documentation on https://our.umbraco.com/documentation/reference/routing/custom-controllers, as I just wanted to test how easy it was to Unit Test Umbraco v8 without having too much custom code implemented. That way, others could easily copy these tests.

    Controller Tests:

    public class HomeControllerTests : UmbracoBaseTest {
        private HomeController controller;
    
        [SetUp]
        public override void SetUp() {
            base.SetUp();
            this.controller = new HomeController();
        }
    
        [Test]
        public void GivenContentModel_WhenIndex_ThenReturnHomeViewModel() {
            var content = new Mock<IPublishedContent>();
            var model = new ContentModel(content.Object);
    
            var result = (ViewResult)this.controller.Index(model);
    
            Assert.IsAssignableFrom<HomeViewModel>(result.Model);
        }
    }
    

    View Model Tests:

    public class HomeViewModelTests : UmbracoBaseTest {
        private Mock<IPublishedContent> content;
    
        [SetUp]
        public override void SetUp() {
            base.SetUp();
            this.content = new Mock<IPublishedContent>();
        }
    
        [Test]
        [TestCase("", null)]
        [TestCase(null, null)]
        [TestCase("My Heading", "My Heading")]
        [TestCase("Another Heading", "Another Heading")]
        public void GivenPublishedContent_WhenGetHeading_ThenReturnPublishedContentHeadingValue(string value, string expected) {
            this.SetPropertyValue(nameof(HomeViewModel.Heading), value);
    
            var model = new HomeViewModel(this.content.Object);
    
            Assert.AreEqual(expected, model.Heading);
        }
    
        private void SetPropertyValue(string alias, string value, string culture = null, string segment = null) {
            var property = new Mock<IPublishedProperty>();
            property.Setup(x => x.Alias).Returns(alias);
            property.Setup(x => x.GetValue(culture, segment)).Returns(value);
            property.Setup(x => x.HasValue(culture, segment)).Returns(!string.IsNullOrEmpty(value));
            this.content.Setup(x => x.GetProperty(alias)).Returns(property.Object);
        }
    }
    

    Base Test Class:

    [TestFixture]
    public abstract class UmbracoBaseTest {
        [SetUp]
        public virtual void SetUp() {
            Current.Factory = new Mock<IFactory>().Object;
        }
    
        [TearDown]
        public virtual void TearDown() {
            Current.Reset();
        }
    }
    

    Home Controller:

    public class HomeController : RenderMvcController {
        public override ActionResult Index(ContentModel model) {
            var viewModel = new HomeViewModel(model.Content);
            return View(viewModel);
        }
    }
    

    View Model:

    public class HomeViewModel : ContentModel {
        public HomeViewModel(IPublishedContent content) : base(content) { }
    
        public string Heading => this.Content.Value<string>(nameof(Heading));
    }
    

    Hopefully it could be helpful to someone.

    All the best, Dennis

  • Alexandre Locas 18 posts 99 karma points
    Mar 27, 2019 @ 12:47
    Alexandre Locas
    0

    Hi, this is great. Thanks for sharing.

    How would you go about testing a function that relies on UmbracoHelper ?

    Than you

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 27, 2019 @ 18:14
    Dennis Adolfi
    0

    Im not sure yet in v8, but I’m working on it. :)

    I’ll let you know when I have it!

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 28, 2019 @ 09:07
    Dennis Adolfi
    2

    Hi Alexander.

    May I ask you what part of the UmbracoHelper you need to write a test for?

    The UmbracoHelper bring a lot a hazzle to unit testing, since the UmbracoHelper is pretty much just a huge wrapper around a bunch of other services.

    For instance, if you check the source code for the method umbracoHelper.GetDictionaryValue(string key) you will see that all it really does is return the value from the ICultureDictionary instance:

    private ICultureDictionary _cultureDictionary;
    
        /// <summary>
        /// Returns the dictionary value for the key specified
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string GetDictionaryValue(string key)
        {
            if (_cultureDictionary == null)
            {
                var factory = CultureDictionaryFactoryResolver.Current.Factory;
                _cultureDictionary = factory.CreateDictionary();
            }
            return _cultureDictionary[key];
        }
    

    (There is a great blog post about unit tests and Umbraco helper here: https://glcheetham.name/2017/01/29/mocking-umbracohelper-using-dependency-injection-the-right-way/)

    So, the UmbracoHelper is great when you are in a view or something and you just want to easily fetch a dictionary value. But when it comes to unit testing, its not so great. If all you need in your controller / service is to fetch a dictionary value, why ask for a super-object that has a gazillion difference wrapped services? Why not just make your code reliable on that ONE dependency, the IDictionaryCulture with IoC and skip the UmbracoHelper completely?

    Here is and example:

    Here I have a controller that needs to add a dictionary value in the view model:

    public class HomeController : RenderMvcController {
        private readonly ICultureDictionary cultureDictionary;
    
        public HomeController(ICultureDictionary cultureDictionary) {
            this.cultureDictionary = cultureDictionary;
        }
    
        public override ActionResult Index(ContentModel model) {
            var viewModel = new HomeViewModel(model.Content)
            {
                DictionaryValue = this.cultureDictionary["hello"]
            };
            return View(viewModel);
        }
    }
    

    The ICultureDictionary needs to be registered in your IoC container, which is done in Umbraco 8 with Composers:

    public class CultureDictionaryComposer : IUserComposer {
            public void Compose(Composition composition) {
                composition.Register<ICultureDictionary, DefaultCultureDictionary>(Lifetime.Scope);
            }
        }
    

    Now I can write my unit tests without having to mock the huge UmbracoHelper, I just need to mock the ICultureDictionary:

    [SetUp]
        public void SetUp() {
            Current.Factory = new Mock<IFactory>().Object;
            this.cultureDictionary = new Mock<ICultureDictionary>();
            this.controller = new HomeController(this.cultureDictionary.Object);
        }
    
        [Test]
        [TestCase("hello", "Something", "Something")]
        [TestCase("hello", "Else", "Else")]
        public void GivenCultureDictionaryKey_WhenIndexAction_ThenViewModelContainsExpectedDictionaryValue(string key, string value, string expected) {
            var content = new Mock<IPublishedContent>();
            var model = new ContentModel(content.Object);
            this.cultureDictionary.Setup(x => x[key]).Returns(value);
    
            var result = (HomeViewModel)((ViewResult)this.controller.Index(model)).Model;
    
            Assert.AreEqual(expected, result.DictionaryValue);
        }
    

    So basiclly this doesnt really answer your question, since you asked how to unit test the UmbracoHelper. There are examples available if you google how to unit test the UmbracoHelper, or check the source code, but they usually consist of a million lines of code for mocking every possible services that the UmbracoHelper is reliable of.

    With this mentioned approach, you'll have you mock a lot less, but it required that you havent already started using the UmbracoHelper all over your code. You need to decouple your code from the UmbracoHelper in a initial setup.

    Another approach. In a few older projects, I've actually created my own IUmbracoHelper and registered a UmbracoHelperAdapter to completely decouple my code from the UmbracoHelper, which adds a little extra coding everytime you need to use a method in the helper that you havent exposed yet, but it made unit testing 10 times easier, since all my services and controllers where just dependent on a interface instead of the actual UmbracoHelper.

    Hope this was any help to you, would be really interesting hearing how other tackle this issue. I love discussing unit tests. :)

  • Alexandre Locas 18 posts 99 karma points
    Mar 28, 2019 @ 14:51
    Alexandre Locas
    1

    Wow that is very enlightning and well explained. It seems to work very well. Thanks.

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Mar 28, 2019 @ 15:39
    Dennis Adolfi
    0

    Thank you! I’m glad you liked it!

    Cheers!

  • Simon Andrews 13 posts 106 karma points
    Mar 27, 2019 @ 10:30
    Simon Andrews
    1

    Writing unit tests would be a lot easier if the Umbraco class constructors were not all Internal for the core objects.

  • Emma Garland 34 posts 108 karma points MVP 2x c-trib
    May 03, 2019 @ 13:40
    Emma Garland
    1

    Thanks for these useful examples!! They have helped me get to this point that might help someone further - I've got the above examples working for objects created by ModelsBuilder, populating the properties and then successfully mapping it based on this article and some previous attempts. NB all I'm testing is a simple mapping extension method ToDto() that converts my Umbraco model into my own DTO without using a third-party mapping tool.

    NB usually I wrap UmbracoHelper into a wrapper object with an interface for testing, and in Umbraco 7 I setup all the context etc following Lars-Erik https://github.com/lars-erik/umbraco-unit-testing-samples. I haven't tried this yet in Umbraco 8. This example is for testing where I explicitly need access to the model properties.

    The pattern I follow is as above but for models builder, using Nunit & Moq & FluentAssertions:

    • The objects I need are of type IPublishedElement (e.g. a nested content item) or IPublishedContent (standard content item), both consist of 0-many IPublishedProperty properties
    • Setup the alias, the GetValue and HasValue method for a mocked IPublishedProperty (unless it is a built in type, which you can setup directly)
    • Setup the content type of the mocked IPublishedElement/IPublishedContent
    • Setup the expected properties to be returned from the mock IPublishedElement/IPublishedContent
    • Create the required model object from the IPublishedElement/IPublishedContent (passed in as a parameter)

    From a best practice POV I don't know if I enjoy still having to new up the model but it is the only way I can find to easily test the generated models and mock them up, so I can access the properties I need to in my tests. So, we're planning on creating a little helper to automatically do this more generically based on the type passed in. I'm also looking through the Umbraco source code as I know there are big suites of tests.

    Anyway, this is the resulting code combined from above and my own stuff, for testing the simple mapping of a models builder generated EntityContent object, to a custom DTO created called ContentEntity, which contains of an Entities property consisting of 0-many Entity objects (picked in the backoffice via nested content).

        public class MappingExtensionsTests
    {
        private EntityContent entityContent;
        private Mock<IFactory> factory;
        private Mock<IPublishedElement> mockIPublishedElement;
        private Mock<IPublishedContent> mockEntity;
        private Entity entity;
    
        [SetUp]
        public void Setup()
        {
            factory = new Mock<IFactory>();
            Current.Factory = factory.Object;
        }
    
        [TearDown]
        public void TearDown()
        {
            Current.Reset();
        }
    
        private void CreateSut()
        {
            mockIPublishedElement = new Mock<IPublishedElement>();
            mockEntity = new Mock<IPublishedContent>();
            var entityTextProperty = new Mock<IPublishedProperty>();
            entityTextProperty.Setup(x => x.Alias).Returns("entityText");
            entityTextProperty.Setup(x => x.GetValue(It.IsAny<string>(), It.IsAny<string>())).Returns(new HtmlString("<b>Expected email text</b>"));
            entityTextProperty.Setup(x => x.HasValue(It.IsAny<string>(), It.IsAny<string>())).Returns(!string.IsNullOrEmpty("entityText"));
    
            var innerEntityPropertyEmailText = new Mock<IPublishedProperty>();
            innerEntityPropertyEmailText.Setup(x => x.Alias).Returns("emailText");
            innerEntityPropertyEmailText.Setup(x => x.GetValue(It.IsAny<string>(), It.IsAny<string>())).Returns(new HtmlString("An entity's HTML string"));
            innerEntityPropertyEmailText.Setup(x => x.HasValue(It.IsAny<string>(), It.IsAny<string>())).Returns(!string.IsNullOrEmpty("emailText"));
    
            mockEntity.Setup(x => x.Id).Returns(7777);
            mockEntity.Setup(x => x.Name).Returns("EntityName");
    
            mockEntity.Setup(x => x.GetProperty(It.Is<string>(s => s.Equals("emailText")))).Returns(innerEntityPropertyEmailText.Object);
            mockEntity.Setup(x => x.Properties).Returns(new List<IPublishedProperty> { innerEntityPropertyEmailText.Object });
            mockEntity.Setup(x => x.ContentType).Returns(new PublishedContentType(5678, "Entity", PublishedItemType.Content,
                Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
    
            entity = new Entity(mockEntity.Object);
    
            var entityProperty = new Mock<IPublishedProperty>();
            entityProperty.Setup(x => x.Alias).Returns("entity");
            entityProperty.Setup(x => x.GetValue(It.IsAny<string>(), It.IsAny<string>())).Returns(new List<Entity>()
            {
                entity
            });
            entityProperty.Setup(x => x.HasValue(It.IsAny<string>(), It.IsAny<string>())).Returns(!string.IsNullOrEmpty("entity"));
    
            mockIPublishedElement.Setup(x => x.Properties).Returns(new List<IPublishedProperty> { entityProperty.Object, entityTextProperty.Object });
            mockIPublishedElement.Setup(x => x.ContentType).Returns(new PublishedContentType(1234, "ContentEntity", PublishedItemType.Content,
                Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
            mockIPublishedElement.Setup(x => x.GetProperty(It.Is<string>(s => s.Equals("entity")))).Returns(entityProperty.Object);
            mockIPublishedElement.Setup(x => x.GetProperty(It.Is<string>(s => s.Equals("entityText")))).Returns(entityTextProperty.Object);
            entityContent = new EntityContent(mockIPublishedElement.Object);
        }
    
        [Test]
        public void WhenIConvertAnEntityContent_WithNullEntities_ThenItShouldReturnNull()
        {
            // arrange
            CreateSut();
            var nullEntityContent = new EntityContent(new Mock<IPublishedElement>().Object);
    
            // act
            var contentEntity = nullEntityContent.ToDto();
    
            // assert
            contentEntity.Should().Be(null);
        }
    
        [Test]
        public void WhenIConvertAnEntityContent_WithEntities_ThenItShouldBecomeANewContentEntity()
        {
            // arrange
            CreateSut();
            var expected = new ContentEntity()
            {
                EmailText = "<b>Expected email text</b>",
                Name = "EntityName",
                Id = 7777
            };
    
            // act
            var contentEntity = entityContent.ToDto();
    
            // assert
            contentEntity.EmailText.Should().Be(expected.EmailText);
            contentEntity.Id.Should().Be(expected.Id);
            contentEntity.Name.Should().Be(expected.Name);
        }
    }
    
  • Emma Garland 34 posts 108 karma points MVP 2x c-trib
    May 03, 2019 @ 16:00
    Emma Garland
    1

    Updated to make a bit more generic:

    public class MappingExtensionsTests
    {
        private EntityContent entityContent;
        private Entity entity;
        private Mock<IFactory> factory;
        private readonly int intId = RandomInt();
    
        [SetUp]
        public void Setup()
        {
            factory = new Mock<IFactory>();
            Current.Factory = factory.Object;
        }
    
        [TearDown]
        public void TearDown()
        {
            Current.Reset();
        }
    
        private void CreateSut()
        {
            var entityHtmlString = new HtmlString("An entity's HTML string");
            var emailHtmlString = new HtmlString("<b>Expected email text</b>");
            var innerEntityPropertyEmailText = SetupPublishedProperty("emailText", entityHtmlString);
            var entityTextProperty = SetupPublishedProperty("entityText", emailHtmlString);
            entity = GenerateContentProperties<Entity>(new[] { innerEntityPropertyEmailText.Object }, "Entity");
            var entityProperty = SetupPublishedProperty("entity", new List<Entity> { entity });
            entityContent = GenerateElementProperties<EntityContent>(new[] { entityProperty.Object, entityTextProperty.Object }, "ContentEntity");
        }
    
        /// <summary>
        /// Setup published property based on alias and object passed in
        /// </summary>
        /// <param name="alias">The alias of the property</param>
        /// <param name="requiredValue">The object you want to return for this given property</param>
        /// <returns></returns>
        private static Mock<IPublishedProperty> SetupPublishedProperty(string alias, object requiredValue)
        {
            var property = new Mock<IPublishedProperty>();
            property.Setup(x => x.Alias).Returns(alias);
            property.Setup(x => x.GetValue(It.IsAny<string>(), It.IsAny<string>()))
                .Returns(requiredValue);
            property.Setup(x => x.HasValue(It.IsAny<string>(), It.IsAny<string>()))
                .Returns(!string.IsNullOrEmpty(alias));
            return property;
        }
    
        /// <summary>
        /// Generate content properties for an IPublishedElement type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="publishedProperties"></param>
        /// <param name="alias"></param>
        /// <returns></returns>
        private T GenerateElementProperties<T>(IPublishedProperty[] publishedProperties, string alias)
        {
            var mockIPublishedElement = new Mock<IPublishedElement>();
            mockIPublishedElement.Setup(x => x.Properties).Returns(publishedProperties);
            mockIPublishedElement.Setup(x => x.ContentType).Returns(new PublishedContentType(intId,
                alias, PublishedItemType.Content,
                Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
    
            foreach (var publishedProperty in publishedProperties)
            {
                mockIPublishedElement.Setup(x => x.GetProperty(It.Is<string>(s => s.Equals(publishedProperty.Alias))))
                    .Returns(publishedProperty);
            }
            return (T)Activator.CreateInstance(typeof(T), mockIPublishedElement.Object);
        }
    
        /// <summary>
        /// Generate content properties for an IPublishedContent type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="publishedProperties"></param>
        /// <param name="alias"></param>
        /// <returns></returns>
        private T GenerateContentProperties<T>(IPublishedProperty[] publishedProperties, string alias)
        {
            var mockIPublishedContent = new Mock<IPublishedContent>();
            mockIPublishedContent.Setup(x => x.Id).Returns(intId);
            mockIPublishedContent.Setup(x => x.Name).Returns(alias);
            mockIPublishedContent.Setup(x => x.Properties).Returns(publishedProperties);
            mockIPublishedContent.Setup(x => x.ContentType).Returns(new PublishedContentType(intId,
                alias, PublishedItemType.Content,
                Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
    
            foreach (var publishedProperty in publishedProperties)
            {
                mockIPublishedContent.Setup(x => x.GetProperty(It.Is<string>(s => s.Equals(publishedProperty.Alias))))
                    .Returns(publishedProperty);
            }
    
            return (T)Activator.CreateInstance(typeof(T), mockIPublishedContent.Object);
        }
    
        private static int RandomInt()
        {
            return new Random((int)DateTime.Now.Ticks).Next();
        }
    
        [Test]
        public void WhenIConvertAnEntityContent_WithNullEntities_ThenItShouldReturnNull()
        {
            // arrange
            CreateSut();
            var nullEntityContent = new EntityContent(new Mock<IPublishedElement>().Object);
    
            // act
            var contentEntity = nullEntityContent.ToDto();
    
            // assert
            contentEntity.Should().Be(null);
        }
    
        [Test]
        public void WhenIConvertAnEntityContent_WithEntities_ThenItShouldBecomeANewContentEntity()
        {
            // arrange
            CreateSut();
            var expected = new ContentEntity()
            {
                EmailText = "<b>Expected email text</b>",
                Name = "Entity",
                Id = intId
            };
    
            // act
            var contentEntity = entityContent.ToDto();
    
            // assert
            contentEntity.EmailText.Should().Be(expected.EmailText);
            contentEntity.Id.Should().Be(expected.Id);
            contentEntity.Name.Should().Be(expected.Name);
        }
    }
    
  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    May 28, 2019 @ 11:36
    Dennis Adolfi
    1

    Thank you for sharing Emma! #h5yr

  • Davy 12 posts 102 karma points
    May 17, 2019 @ 07:37
    Davy
    0

    Is there a way to test the contentService methods, for example CreateAndSave() and SaveAndPublish().

    I have mocked a IContentService myself but everything in this mocked service is null and I cannot find a way to fix this yet...

    Thanks!

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Jun 03, 2019 @ 14:08
    Dennis Adolfi
    0

    Do you mean as a Integration Test or a Unit test? Do you want to test that something really gets created/saved etc on those events or what is it you are looking to test? Could you share some code examples?

  • Davy 12 posts 102 karma points
    Jun 04, 2019 @ 09:30
    Davy
    0

    I would like to test the saving and retrieving of content in my Umbraco project, as I have made a publish flow myself I would like to test. In this flow I copy everything from one environment to another if one of these methods is called.

    Unfortunately I have not find a way to fully mock the contentService and UmbracoContextFactory if that is even the correct way to test this, let alone be possible?

    [TestFixture]
    public abstract class BaseTest
    {
        protected static IContentService _contentService;
        protected static IUmbracoContextFactory _umbracoContextFactory;
    
        [SetUp]
        public virtual void SetUp()
        {
    
            Current.Factory = new Mock<IFactory>().Object;
            _contentService = new Mock<IContentService>().Object;
            _umbracoContextFactory = new Mock<IUmbracoContextFactory>().Object;
        }
    
        [TearDown]
        public virtual void TearDown()
        {
            Current.Reset();
            _contentService = null;
            _umbracoContextFactory = null;
        }
    }
    
    
    public class PublishFlowControllerTest : BaseTest
    {
        private PublishFlowController _publishFlowController;
    
        private IContent project;
        private IContent development;
        private IContent staging;
        private IContent production;
    
        private IContent developmentSite;
        private IContent stagingSite;
        private IContent productionSite;
    
        [SetUp]
        public override void SetUp()
        {
            base.SetUp();
    
            _publishFlowController = new PublishFlowController(_contentService);
    
            project = _contentService.CreateAndSave("GDL", -1, Project.ModelTypeAlias);
            development = _contentService.CreateAndSave("development", project, ContentModels.Environment.ModelTypeAlias);
            staging = _contentService.CreateAndSave("staging", project, ContentModels.Environment.ModelTypeAlias);
            production = _contentService.CreateAndSave("production", project, ContentModels.Environment.ModelTypeAlias);
    
            developmentSite =_contentService.CreateAndSave("V1000", development, SiteRoot.ModelTypeAlias);
            stagingSite = _contentService.CreateAndSave("V2000", staging, SiteRoot.ModelTypeAlias);
            productionSite = _contentService.CreateAndSave("V3000", production, SiteRoot.ModelTypeAlias);
            _contentService.SaveAndPublishBranch(project, true);
        }
    
        [Test]
        public void RestoreToDevelopment()
        {
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.RestoreToDevelopment());
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.RestoreToDevelopment(developmentSite.Id));
    
            Assert.IsInstanceOf(typeof(OkNegotiatedContentResult<string>), _publishFlowController.RestoreToDevelopment(stagingSite.Id));
            Assert.IsInstanceOf(typeof(OkNegotiatedContentResult<string>), _publishFlowController.RestoreToDevelopment(productionSite.Id));
        }
    
        [Test]
        public void RestoreToStaging()
        {
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.RestoreToStaging());
        }
    
        [Test]
        public void DeployToStaging()
        {
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.DeployToStaging());
        }
    
        [Test]
        public void DeployToProduction()
        {
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.DeployToProduction());
        }
    }
    
  • Emma Garland 34 posts 108 karma points MVP 2x c-trib
    Jun 04, 2019 @ 11:06
    Emma Garland
    0

    Hi Davy,

    Some ideas - you have mocked the content service, but then using .Object straight away you haven't been able set it up to return what you expect. Can you take off .Object and keep Content Service as a mock then replace the setup like this for example (I had to include the optional argument userId though):

    _contentService.Setup(x=>x.CreateAndSave("GDL", -1, "Project.ModelTypeAlias", -1)).Returns(project);
    

    So it is now saying what you expect the mocked Content Service to return when you call the Publish and Save methods on it - rather than calling the actual content service object itself.

    e.g.

    When I call create and save on the content service, it should return me an object equivalent to "project"

    The System Under Test is the publish flow controller, so you do want to call the actual method on that - e.g. Restore to Development. I think if you inject the SUT creation with .Object AFTER the content service setup:

      _publishFlowController = new PublishFlowController(_contentService.Object);
    

    Things should start to work better? Hopefully that helps at all,

    Emma

  • Davy 12 posts 102 karma points
    Jun 04, 2019 @ 12:10
    Davy
    0

    I implemented the Mocks as you said, but I still have the same issue :/

    After the Setups et cetera the variables that need to be returned are null.

    When I run my test the second Assert will state: Message: System.NullReferenceException : Object reference not set to an instance of an object.

    [TestFixture]
    public class PublishFlowControllerTest : BaseTest
    {
        private PublishFlowController _publishFlowController;
    
        private IContent project;
        private IContent development;
        private IContent staging;
        private IContent production;
    
        private IContent developmentSite;
        private IContent stagingSite;
        private IContent productionSite;
    
        [SetUp]
        public override void SetUp()
        {
            base.SetUp();
            _mockContentService.Setup(x => x.CreateAndSave("GDL", -1, Project.ModelTypeAlias, -1)).Returns(project);
            _mockContentService.Setup(x => x.CreateAndSave("development", project, ContentModels.Environment.ModelTypeAlias, -1)).Returns(development);
            _mockContentService.Setup(x => x.CreateAndSave("staging", project, ContentModels.Environment.ModelTypeAlias, -1)).Returns(staging);
            _mockContentService.Setup(x => x.CreateAndSave("production", project, ContentModels.Environment.ModelTypeAlias, -1)).Returns(production);
    
            _mockContentService.Setup(x => x.CreateAndSave("V1000", development, SiteRoot.ModelTypeAlias, -1)).Returns(developmentSite);
            _mockContentService.Setup(x => x.CreateAndSave("V2000", development, SiteRoot.ModelTypeAlias, -1)).Returns(stagingSite);
            _mockContentService.Setup(x => x.CreateAndSave("V3000", development, SiteRoot.ModelTypeAlias, -1)).Returns(productionSite);
    
            _publishFlowController = new PublishFlowController(_mockContentService.Object);
        }
    
        [Test]
        public void RestoreToDevelopment()
        {
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.RestoreToDevelopment());
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.RestoreToDevelopment(developmentSite.Id));
    
            Assert.IsInstanceOf(typeof(OkNegotiatedContentResult<string>), _publishFlowController.RestoreToDevelopment(stagingSite.Id));
            Assert.IsInstanceOf(typeof(OkNegotiatedContentResult<string>), _publishFlowController.RestoreToDevelopment(productionSite.Id));
        }
    
        [Test]
        public void RestoreToStaging()
        {
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.RestoreToStaging());
        }
    
        [Test]
        public void DeployToStaging()
        {
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.DeployToStaging());
        }
    
        [Test]
        public void DeployToProduction()
        {
            Assert.IsInstanceOf(typeof(NotFoundResult), _publishFlowController.DeployToProduction());
        }
    }
    
  • Emma Garland 34 posts 108 karma points MVP 2x c-trib
    Jun 04, 2019 @ 14:01
    Emma Garland
    0

    Hi Davy

    OK, the exception will be happening is because the IContent items need to be instantiated or mocked - at the moment they are just null, so when the assert tries to access .Id on developmentSite, it will throw a null reference exception as the item itself is null.

    You are mocking the content service and telling it what content items to return, and from my understanding you aren't wanting to test the behaviour of the Content Service itself, just testing the Publish Flow Controller (whilst telling the mock content service what to return).

    So you'll need to either:

    Mock the IContent items, and setup for example so when you request .Id, it should return the ID you expect, e.g.

        private readonly Mock<IContent> stagingMock = new Mock<IContent>();
    

    Making sure to setup the properties you want to be returned on these, e.g. the ID to return

    stagingMock.Setup(x => x.Id).Returns(123);
    

    Or (untested) create new instances of a concrete content item, e.g

        private readonly IContent development = new Content("GDL", -1, new Mock<IContentType>().Object, "");
    

    Fill in the bits as required - this is untested code and I'd probably mock instead.

    I like to structure each tests in the fashion:

    //arrange (setup mocks and the system under test)
    //act (call method on SUT - this is the actual unit test)
    //assert (verify the behaviour that you are expecting has occurred)
    

    You might do this anyway but as an aside - I would also split up and name each test method along the lines of what you're testing and what you're expecting as a result, e.g. GivenIAmPublishingWhenIRestoreToStagingWithNoParams_ThenANotFoundResultShouldBeReturned

    Going back to the original post though, from what I can gather, you are testing that when you try and restore to the various environments using your PublishFlowController, the content service should be hit and a NotFoundResult should occur? Just to be clear in each test what behaviour we're testing and what we expect to happen.

    Am I right that you have code in a flow to copy everything from one environment to another if one of these controller's methods is called, so you want to test that the content service is hit by your publish flow controller?

    In that case, you would want to ensure in your Asserts section that the Content Service method should have been hit as many times as you expected.

    I would split the tests into independent units, so the test with the Id passed in would be a separate test with a separate set of expectations in the Assert.

    If not though, I may have misunderstood the behaviour you're trying to test! If you are trying to test the actual content service behaviour calls your controller, I have not done this before, and would mean you'd not be mocking the content service - as that would be the System Under Test.

    Hope that isn't too confusing and gets you a bit further to what you're trying to achieve though.

    Thanks

    Emma

  • Davy 12 posts 102 karma points
    Jun 05, 2019 @ 09:07
    Davy
    0

    Hi,

    I think the problem is when I mock the ContentService most of the attributes are null, including the UmbracoContext, . So, even if want to save the mocked content, it does not because there is nowhere to save it. Or at least that is my theory...

    You are indeed right with what and the way I want to test my controller!

  • Emma Garland 34 posts 108 karma points MVP 2x c-trib
    Jun 05, 2019 @ 13:31
    Emma Garland
    0

    Hi

    Unsure what the controller itself is doing behind the scenes, but good that is what we're trying to test specifically.

    You have passed in a mock Content Service to the controller and told this mock what you want it to do for each method and params combo you expect it to hit.

    Can you debug the test and see exactly what the values being passed into the CreateAndSave method it is throwing an exception on? As it is probably because we haven't setup the expectation on that one, so it is trying to hit a mock content service with the combination of params that we haven't told it what to do in that case.

    e.g.:

    _mockContentService.Setup(x => x.CreateAndSave("GDL", -1, Project.ModelTypeAlias, -1)).Returns(project);
    

    If it doesn't come across this combination of params then the create and save setup will not be useful.

    I recommend that in the "RestoreToStaging" method, add a break point and see what is happening at the point there is an exception, what variable are missing from the mocked service setups, what it is trying to call and with what params. That way you can setup on the mock.

    Emma

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Jun 03, 2019 @ 14:07
    Dennis Adolfi
    2

    FYI: I've packaged a few of these examples into a PR that just got merged to the Umbraco Documentation: https://our.umbraco.com/documentation/Implementation/Unit-Testing

  • Ernesto Lopez 3 posts 73 karma points
    Jun 04, 2019 @ 18:03
    Ernesto Lopez
    0

    Hello Dennis is there any way to verify the content model that uses an interface IHtmlString or IEnumerable

    Thank you so much for you help with the documentation of Unit Test.

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Jun 04, 2019 @ 18:30
    Dennis Adolfi
    0

    Hi Ernesto. Do you mean like aN RTE property on a ContentModel and a list of children for example (IEnumerable

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Jun 04, 2019 @ 18:30
    Dennis Adolfi
    0

    I can look at an example tomorrow morning. Glad you liked the documentation!

  • Ernesto Lopez 3 posts 73 karma points
    Jun 04, 2019 @ 18:49
    Ernesto Lopez
    0

    Yes, I'm trying to understand how to focus the unit test for this case and a Repeatable String is IEnumerable < String >

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Jun 05, 2019 @ 11:33
    Dennis Adolfi
    1

    Hi again Ernesto, sorry for the delay.

    Is this maybe what you had i mind?

    public class MyViewModel : ContentModel
    {
        public MyViewModel(IPublishedContent content) : base(content) { }
    
        public IHtmlString RichTextEditor => this.Content.Value<IHtmlString>("richTextEditor");
    
        public IEnumerable<string> RepeatableStrings => this.Content.Value<IEnumerable<string>>("repeatableStrings");
    }
    
    [TestFixture]
    public class MyViewModelTests
    {
        private Mock<IPublishedContent> content;
    
        [SetUp]
        public void SetUp()
        {
            Current.Factory = new Mock<IFactory>().Object;
            this.content = new Mock<IPublishedContent>();
        }
    
        [TearDown]
        public void TearDown()
        {
            Current.Reset();
        }
    
        [Test]
        [TestCase("")]
        [TestCase(null)]
        [TestCase("<p>This is html</p>")]
        [TestCase("<p>This is other html</p>")]
        public void Given_PublishedContentWithRichTextEditor_When_GetRichTextEditor_Then_ReturnCustomViewModelWithRichTextEditor(string expected)
        {
            this.SetupPropertyValue("richTextEditor", new HtmlString(expected));
    
            var model = new MyViewModel(this.content.Object);
    
            Assert.AreEqual(expected, model.RichTextEditor.ToHtmlString());
        }
    
        [Test]
        public void Given_PublishedContentWithRepeatableStrings_When_GetRepeatableStrings_Then_ReturnCustomViewModelWithWithRepeatableStrings()
        {
            var expected = new List<string>()
            {
                "any",
                "value"
            };
            this.SetupPropertyValue("repeatableStrings", expected);
    
            var model = new MyViewModel(this.content.Object);
    
            Assert.AreEqual(expected, model.RepeatableStrings);
        }
    
        private void SetupPropertyValue(string alias, object value, string culture = null, string segment = null)
        {
            var property = new Mock<IPublishedProperty>();
            property.Setup(x => x.Alias).Returns(alias);
            property.Setup(x => x.GetValue(culture, segment)).Returns(value);
            property.Setup(x => x.HasValue(culture, segment)).Returns(value != null);
            this.content.Setup(x => x.GetProperty(alias)).Returns(property.Object);
        }
    }
    

    Based on this content:

    enter image description here

  • Ernesto Lopez 3 posts 73 karma points
    Jun 05, 2019 @ 14:57
    Ernesto Lopez
    0

    Hi Dennis, thank you so much for your help, that solution is perfect to my problem. Sorry, I'm new with Unit Testing

  • Dennis Adolfi 1045 posts 6176 karma points MVP c-trib
    Jun 05, 2019 @ 15:03
    Dennis Adolfi
    0

    Glad I could help! Have a great day! 👍🏻

Please Sign in or register to post replies

Write your reply to:

Draft