More unit testing using dependency injection

Following my previous post where I explained a few ideas for more practical code-level testing, here's a quick update with a version of a base class I use to base my test classes on (based on the Autofac DI library). Here's the code:

using Autofac;
using NUnit.Framework;

namespace RedMoon.Test.Common
{
    [TestFixture]
    public abstract class AutofacTestBase<T>
    {              
        private void MockExternalDependencies(ContainerBuilder builder)
        {
            // TODO - Load all "default" configured dependencies here
            OverrideDefaultExternalDependencies(builder);
            AutofacContainer.Instance = builder.Build();
        }

        protected abstract void OverrideDefaultExternalDependencies(ContainerBuilder builder);

        public T SystemUnderTest
        {
            get
            {
                MockExternalDependencies(new ContainerBuilder());
                AutofacContainer.Instance.Resolve<T>();
            }
        }
    }
}

The use of generics makes it clear which "entry point" you are using for your tests, and removes the need to create a "SystemUnderTest" variable over and over again. Remember that, if you want to follow the approach I outlined in the previous post, pick an "entry point" at the boundary or "port" of a meaningful unit of functionality, typically a specific feature of your software. As I'm usually working with Sitecore MVC applications, my usual approach is that my Controller classes are "dumb" and simply dispatch control to a "MyFeatureService" class which is independent of .NET MVC. The "MyFeatureService" class is usually the one which is the "entry point" for my unit tests.

Any test class which subclasses AutofacTestBase is forced to implement OverrideDefaultExternalDependencies. This method is where you register any mock dependencies.  Remember that, if you want to follow the approach I outlined in the previous post, be sure to only mock "external" dependencies - such as access to external APIs, REST services, databases, configuration files etc. You shouldn't mock any classes which you have written which don't fall into the "external dependencies" listed previously, as these can be considered "implementation" and should be exercised via your "entry point" class. As with any approach, feel free to break any of these rules to suit your own circumstances :)

If all goes well, you should end up with meaningful tests, which continue to pass when you refactor code (rather than refuse to compile) and fail whenever you introduce any bugs.

Here's the test class I used in the previous post, rewritten to use the new base class:

namespace RedMoon.Example.Tests.Pipelines.RenderField
{
    public class TokenReplacerHelperTests : AutofacTestBase<ITokenReplacerHelper>
    {
        private readonly Guid _tokenItem1 = Guid.NewGuid();
        private readonly Guid _tokenItem2 = Guid.NewGuid();

        private Mock<ISitecoreService> _sitecoreService;
        private Mock<ISiteContext> _siteContext;
        private Mock<ICache> _cache;

        private Mock<IRenderFieldResult> _renderFieldResult;
        private Mock<IRenderFieldArgs> _renderFieldArgs;

        [SetUp]
        public void SetUp()
        {
            SetUpValidTokenSubstitutionConditions();
        }

        protected override void OverrideDefaultExternalDependencies(ContainerBuilder builder)
        {
            builder.RegisterInstance(_sitecoreService.Object);
            builder.RegisterInstance(_siteContext.Object);
            builder.RegisterInstance(_cache.Object);
        }

        [Test]
        public void WhenReplaceTokensIsCalledThenArgsResultIsSetCorrectly()
        {
            ReplaceTokens();

            _renderFieldResult.VerifySet(r => r.FirstPart = "Something token text 1 something else token text 2 token text 2");
        }

        [Test]
        public void WhenReplaceTokensIsCalledAndCacheHasTokensThenArgsResultIsSetCorrectly()
        {
            var dictionary = new Dictionary<string, string>
            {
                {"$TokenKey1$", "token text 1"},
                {"$TokenKey2$", "token text 2"}
            };
            _sitecoreService = new Mock<ISitecoreService>();
            _cache.Setup(c => c.Retrieve<Dictionary<string, string>>("Tokens"))
                .Returns(dictionary);

            ReplaceTokens();

            _renderFieldResult.VerifySet(r => r.FirstPart = "Something token text 1 something else token text 2 token text 2");
        }

        [Test]
        public void WhenTokensIsCalledAndFieldTypeIsNotReplaceableThenArgsResultIsNotSet()
        {
            _renderFieldArgs.Setup(a => a.FieldTypeKey).Returns("Image");

            ReplaceTokens();

            _renderFieldResult.VerifySet(r => r.FirstPart = It.IsAny<string>(), Times.Never);
        }

        [Test]
        public void WhenTokensIsCalledAndPageModeIsEditThenArgsResultIsNotSet()
        {
            _siteContext.Setup(c => c.PageModeIsPageEditor).Returns(true);

            ReplaceTokens();

            _renderFieldResult.VerifySet(r => r.FirstPart = It.IsAny<string>(), Times.Never);
        }

        [Test]
        public void WhenTokensIsCalledAndFieldValueIsEmptyThenArgsResultIsNotSet()
        {
            _renderFieldArgs.Setup(a => a.Result.FirstPart).Returns(string.Empty);

            ReplaceTokens();

            _renderFieldResult.VerifySet(r => r.FirstPart = It.IsAny<string>(), Times.Never);
        }

        [Test]
        public void WhenTokensIsCalledAndFieldValueIsNullThenArgsResultIsNotSet()
        {
            _renderFieldArgs.Setup(a => a.Result.FirstPart).Returns(null as string);

            ReplaceTokens();

            _renderFieldResult.VerifySet(r => r.FirstPart = It.IsAny<string>(), Times.Never);
        }

        [Test]
        public void WhenTokensIsCalledAndNoTokenItemsInSitecoreThenArgsResultIsNotSet()
        {
            _sitecoreService.Setup(s => s.GetChildren(new Guid("{6139204B-024E-44E1-BD14-61BEE8929B9E}"))).Returns(new Guid[0]);

            ReplaceTokens();

            _renderFieldResult.VerifySet(r => r.FirstPart = It.IsAny<string>(), Times.Never);
        }

        private void SetUpValidTokenSubstitutionConditions()
        {
            SetUpDependencies();
            SetUpArgs();
        }

        private void SetUpDependencies()
        {
            _sitecoreService = new Mock<ISitecoreService>();
            _sitecoreService.Setup(s => s.GetChildren(new Guid("{6139204B-024E-44E1-BD14-61BEE8929B9E}")))
                .Returns(new[] { _tokenItem1, _tokenItem2 });
            _sitecoreService.Setup(s => s.RawFieldValue(_tokenItem1, "Key")).Returns("$TokenKey1$");
            _sitecoreService.Setup(s => s.RawFieldValue(_tokenItem1, "Text")).Returns("token text 1");
            _sitecoreService.Setup(s => s.RawFieldValue(_tokenItem2, "Key")).Returns("$TokenKey2$");
            _sitecoreService.Setup(s => s.RawFieldValue(_tokenItem2, "Text")).Returns("token text 2");

            _siteContext = new Mock<ISiteContext>();
            _siteContext.Setup(c => c.PageModeIsPageEditor).Returns(false);

            _cache = new Mock<ICache>();
            _cache.Setup(c => c.Retrieve<Dictionary<string, string>>("Tokens"))
                .Returns(null as Dictionary<string, string>);
        }

        private void SetUpArgs()
        {
            _renderFieldResult = new Mock<IRenderFieldResult>();
            _renderFieldResult.Setup(a => a.FirstPart).Returns("Something $TokenKey1$ something else $TokenKey2$ $TokenKEY2$");

            _renderFieldArgs = new Mock<IRenderFieldArgs>();
            _renderFieldArgs.Setup(a => a.FieldTypeKey).Returns("single-line text");
            _renderFieldArgs.Setup(a => a.Result).Returns(_renderFieldResult.Object);
        }


        private void ReplaceTokens()
        {
            SystemUnderTest.ReplaceTokens(_renderFieldArgs.Object);
        }
    }
}

 


By James at 4 Sep 2016, 20:58 PM


Comments

Post a comment