0Neji
0Neji

Reputation: 1116

Testing Code with Third Party Object Instantiation

New to unit testing and trying to get my head around some simple tests for a piece of code which gets or creates a template if it doesn't exist (in Umbraco 8).

The method is quite simple, when Initialise is called, it gets the template and if it doesn't exist, creates it:

using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Services;

namespace Papermoon.Umbraco.Aldus.Core.Components
{
    public class TemplateComponent : IComponent
    {
        private readonly IFileService _fileService;

        public TemplateComponent(IFileService fileService)
        {
            _fileService = fileService;
        }

        public void Initialize()
        {
            ITemplate blogTemplate = _fileService.GetTemplate("aldusBlog");

            if (blogTemplate == null)
            {
                blogTemplate = new Template("Aldus Blog", "aldusBlog");

                _fileService.SaveTemplate(blogTemplate);
            }
        }

        public void Terminate() { }
    }
}

Works okay, no problem.

I'm trying to write a few tests, the first checks if _fileService.GetTemplate is called.

The second test should check that _fileService.SaveTemplate() is called if that returns null.

using Moq;
using NUnit.Framework;
using Papermoon.Umbraco.Aldus.Core.Components;
using Umbraco.Core.Models;
using Umbraco.Core.Services;

namespace Papermoon.Umbraco.Aldus.Core.Tests.Components
{
    [TestFixture]
    public class TemplateComponentTests
    {
        private Mock<IFileService> _fileService;

        private TemplateComponent _component;

        [SetUp]
        public void SetUp()
        {
            _fileService = new Mock<IFileService>();

            _component = new TemplateComponent(_fileService.Object);
        }

        [Test]
        public void Initialise_WhenCalled_GetsBlogTemplate()
        {
            _component.Initialize();

            _fileService.Verify(s => s.GetTemplate("aldusBlog"), Times.Once);
        }

        [Test]
        public void Initialise_BlogTemplateDoesNotExist_CreateTemplate()
        {
            _fileService
                .Setup(s => s.GetTemplate("aldusBlog"))
                .Returns((ITemplate) null);

            _component.Initialize();

            _fileService.Verify(s => s.SaveTemplate(It.Is<ITemplate>(p => p.Alias == "aldusBlog"), -1), Times.Once());
        }
    }
}

The trouble when I do this is that the blogTemplate = new Template("Aldus Blog", "aldusBlog"); throws an error:

Can not get Current.Config during composition. Use composition.Config.

I assume this is because I don't have any kind of context which leads me to think that the ITemplate needs to be mocked. However, because new Template("Aldus Blog", "aldusBlog"); will always be called, it will always throw this error.

Obviously the code isn't bullet proof, so how do I refactor this to be testable?

Upvotes: 3

Views: 65

Answers (1)

Nkosi
Nkosi

Reputation: 247018

That 3rd party class is probably tightly coupled to an implementation concern that does not exist or is not configured when unit testing in isolation.

abstract that object creation out into a factory.

public interface ITemplateFactory {
    ITemplate Create(string name, string alias);
}

whose implementation can be injected at run-time

public class DefaultTemplateFactory : ITemplateFactory {
    public ITemplate Create(string name, string alias) {
        return new Template(name, alias);
    }
}

Provided it is registered at the composition root during startup

This now allow the component to be loosely coupled away from implementation concerns

public class TemplateComponent : IComponent {
    private readonly IFileService fileService;
    private readonly ITemplateFactory templateFactory;

    public TemplateComponent(IFileService fileService, ITemplateFactory templateFactory) {
        this.fileService = fileService;
        this.templateFactory = templateFactory;
    }

    public void Initialize() {
        ITemplate blogTemplate = fileService.GetTemplate("aldusBlog");

        if (blogTemplate == null) {
            blogTemplate = templateFactory.Create("Aldus Blog", "aldusBlog");

            fileService.SaveTemplate(blogTemplate);
        }
    }

    public void Terminate() { }
}

That can be replaced as needed when testing in isolation

[TestFixture]
public class TemplateComponentTests {

    private Mock<IFileService> fileService;
    private Mock<ITemplateFactory> templateFactory;
    private TemplateComponent component;
    string templateAlias = "aldusBlog";

    [SetUp]
    public void SetUp() {
        //Arrange
        fileService = new Mock<IFileService>();

        templateFactory = new Mock<ITemplateFactory>();
        templateFactory.Setup(_ => _.Create(It.IsAny<string>(), It.IsAny<string>()))
            .Returns((string name, string alias) => 
                Mock.Of<ITemplate>(_ => _.Alias == alias && _.Name == name)
            );

        component = new TemplateComponent(fileService.Object, templateFactory.Object);
    }

    [Test]
    public void Initialise_WhenCalled_GetsBlogTemplate() {
        //Act
        component.Initialize();
        //Assert
        fileService.Verify(s => s.GetTemplate(templateAlias), Times.Once);
    }

    [Test]
    public void Initialise_BlogTemplateDoesNotExist_CreateTemplate() {
        //Act
        component.Initialize();
        //Assert
        fileService.Verify(s => s.SaveTemplate(It.Is<ITemplate>(p => p.Alias == templateAlias), 0), Times.Once());
    }
}

Upvotes: 2

Related Questions