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 1035 posts 6154 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 1035 posts 6154 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 167 posts 661 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 14 posts 95 karma points
    Mar 18, 2019 @ 18:34
    Alexandre Locas
    0

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

  • Dennis Adolfi 1035 posts 6154 karma points MVP c-trib
    Mar 20, 2019 @ 14:17
    Dennis Adolfi
    0

    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 11 posts 103 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 1035 posts 6154 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 11 posts 103 karma points
    30 days ago
    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 1035 posts 6154 karma points MVP c-trib
    30 days ago
    Dennis Adolfi
    101

    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 1035 posts 6154 karma points MVP c-trib
    30 days ago
    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 11 posts 103 karma points
    29 days ago
    Simon Andrews
    1

    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 1035 posts 6154 karma points MVP c-trib
    24 days ago
    Dennis Adolfi
    2

    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 14 posts 95 karma points
    24 days ago
    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 1035 posts 6154 karma points MVP c-trib
    23 days ago
    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 1035 posts 6154 karma points MVP c-trib
    23 days ago
    Dennis Adolfi
    1

    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 14 posts 95 karma points
    23 days ago
    Alexandre Locas
    1

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

  • Dennis Adolfi 1035 posts 6154 karma points MVP c-trib
    22 days ago
    Dennis Adolfi
    0

    Thank you! I’m glad you liked it!

    Cheers!

  • Simon Andrews 11 posts 103 karma points
    24 days ago
    Simon Andrews
    1

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

Please Sign in or register to post replies

Write your reply to:

Draft