versionFrom | needsV9Update | meta.Title | meta.Description |
---|---|---|---|
8.0.0 |
true |
Unit Testing Umbraco |
A guide to getting started with unit testing in Umbraco |
These examples are for Umbraco 8+ and they rely on NUnit and Moq.
:::note Besides the documentation there's a community project available on GitHub gathering all the examples from the Unit Testing documentation in a ready to run solution. Read more about the project on Dennis Adolfi's blog. :::
When testing components in Umbraco, especially controllers, there are a few dependencies that needs to be mocked / faked in order to get your unit tests running. Every Umbraco controller has two constructors: one empty constructor without any parameters for anyone not interested in unit testing or dependency injections, and one with full constructor injection which contains all parameters needed for proper unit testing. A lot of these dependencies are interfaces, which are mocked using Mock.Of<>, but there are still a few explicit non-interface dependencies that needs to be faked. In this documentation all mocks and fakes have been placed in a base class to avoid having to repeat this setup in every test class.
public abstract class UmbracoBaseTest
{
public ServiceContext ServiceContext;
public MembershipHelper MembershipHelper;
public UmbracoHelper UmbracoHelper;
public UmbracoMapper UmbracoMapper;
public Mock<ICultureDictionary> CultureDictionary;
public Mock<ICultureDictionaryFactory> CultureDictionaryFactory;
public Mock<IPublishedContentQuery> PublishedContentQuery;
public Mock<HttpContextBase> HttpContext;
public Mock<IMemberService> memberService;
public Mock<IPublishedMemberCache> memberCache;
[SetUp]
public virtual void SetUp()
{
this.SetupHttpContext();
this.SetupCultureDictionaries();
this.SetupPublishedContentQuerying();
this.SetupMembership();
this.ServiceContext = ServiceContext.CreatePartial();
this.UmbracoHelper = new UmbracoHelper(Mock.Of<IPublishedContent>(), Mock.Of<ITagQuery>(), this.CultureDictionaryFactory.Object, Mock.Of<IUmbracoComponentRenderer>(), this.PublishedContentQuery.Object, this.MembershipHelper);
this.UmbracoMapper = new UmbracoMapper(new MapDefinitionCollection(new List<IMapDefinition>()));
}
public virtual void SetupHttpContext()
{
this.HttpContext = new Mock<HttpContextBase>();
}
public virtual void SetupCultureDictionaries()
{
this.CultureDictionary = new Mock<ICultureDictionary>();
this.CultureDictionaryFactory = new Mock<ICultureDictionaryFactory>();
this.CultureDictionaryFactory.Setup(x => x.CreateDictionary()).Returns(this.CultureDictionary.Object);
}
public virtual void SetupPublishedContentQuerying()
{
this.PublishedContentQuery = new Mock<IPublishedContentQuery>();
}
public virtual void SetupMembership()
{
this.memberService = new Mock<IMemberService>();
var memberTypeService = Mock.Of<IMemberTypeService>();
var membershipProvider = new MembersMembershipProvider(memberService.Object, memberTypeService);
this.memberCache = new Mock<IPublishedMemberCache>();
this.MembershipHelper = new MembershipHelper(this.HttpContext.Object, this.memberCache.Object, membershipProvider, Mock.Of<RoleProvider>(), memberService.Object, memberTypeService, Mock.Of<IUserService>(), Mock.Of<IPublicAccessService>(), AppCaches.NoCache, Mock.Of<ILogger>());
}
public void SetupPropertyValue(Mock<IPublishedContent> publishedContentMock, 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);
publishedContentMock.Setup(x => x.GetProperty(alias)).Returns(property.Object);
}
}
:::tip
ServiceContext.CreatePartial()
has several optional parameters, and by naming them you only need to mock the dependencies that you actually need, for example: ServiceContext.CreatePartial(contentService: Mock.Of<IContentService>());
:::
See Reference documentation on Returning a view with a custom model.
public class MyCustomViewModel : ContentModel
{
public MyCustomViewModel(IPublishedContent content) : base(content) { }
public string Heading => this.Content.Value<string>(nameof(Heading));
public string Url => this.Content.Url;
}
[TestFixture]
public class MyCustomModelTests : UmbracoBaseTest
{
[SetUp]
public override void SetUp()
{
base.SetUp();
}
[Test]
[TestCase("", "")]
[TestCase("My Heading", "My Heading")]
[TestCase("Another Heading", "Another Heading")]
public void GivenPublishedContent_WhenGetHeading_ThenReturnCustomViewModelWithHeadingValue(string value, string expected)
{
var publishedContent = new Mock<IPublishedContent>();
base.SetupPropertyValue(publishedContent, nameof(MyCustomViewModel.Heading), value);
var model = new MyCustomViewModel(publishedContent.Object);
Assert.AreEqual(expected, model.Heading);
}
[Test]
[TestCase("/", "/")]
[TestCase("/umbraco", "/umbraco")]
[TestCase("https://www.umbraco.com", "https://www.umbraco.com")]
public void GivenPublishedContent_WhenGetUrl_ThenReturnCustomViewModelWithUrlValue(string value, string expected)
{
var publishedContent = new Mock<IPublishedContent>();
publishedContent.Setup(x => x.Url).Returns(value);
var model = new MyCustomViewModel(content.Object);
Assert.AreEqual(expected, model.Url);
}
}
See Reference documentation for Custom controllers (Hijacking Umbraco Routes).
public class HomeController : RenderMvcController
{
public HomeController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext serviceContext, AppCaches appCaches, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, serviceContext, appCaches, profilingLogger, umbracoHelper) { }
public override ActionResult Index(ContentModel model)
{
var myCustomModel = new MyCustomModel(model.Content);
myCustomModel.MyProperty1 = "Hello World";
return View(myCustomModel);
}
}
public class MyCustomModel : ContentModel
{
public MyCustomModel(IPublishedContent content) : base(content) { }
public string MyProperty1 { get; set; }
}
[TestFixture]
public class HomeControllerTests : UmbracoBaseTest
{
private HomeController controller;
[SetUp]
public override void SetUp()
{
base.SetUp();
this.controller = new HomeController(Mock.Of<IGlobalSettings>(), Mock.Of<IUmbracoContextAccessor>(), base.ServiceContext, AppCaches.NoCache, Mock.Of<IProfilingLogger>(), base.UmbracoHelper);
}
[Test]
public void WhenIndexAction_ThenResultIsIsAssignableFromContentResult()
{
var model = new ContentModel(new Mock<IPublishedContent>().Object);
var result = this.controller.Index(model);
Assert.IsAssignableFrom<ViewResult>(result);
}
[Test]
public void GivenContentModel_WhenIndex_ThenReturnViewModelWithMyProperty()
{
var model = new ContentModel(new Mock<IPublishedContent>().Object);
var result = (MyCustomModel)((ViewResult)this.controller.Index(model)).Model;
Assert.AreEqual("Hello World", result.MyProperty1);
}
}
See Reference documentation on SurfaceControllers.
public class MySurfaceController : SurfaceController
{
public MySurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory umbracoDatabaseFactory, ServiceContext serviceContext, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) : base(umbracoContextAccessor, umbracoDatabaseFactory, serviceContext, appCaches, logger, profilingLogger, umbracoHelper) { }
public ActionResult Index()
{
return Content("Hello World");
}
}
[TestFixture]
public class MySurfaceControllerTests : UmbracoBaseTest
{
private MySurfaceController controller;
[SetUp]
public override void SetUp()
{
base.SetUp();
this.controller = new MySurfaceController(Mock.Of<IUmbracoContextAccessor>(), Mock.Of<IUmbracoDatabaseFactory>(), base.ServiceContext, AppCaches.NoCache, Mock.Of<ILogger>(), Mock.Of<IProfilingLogger>(), base.UmbracoHelper);
}
[Test]
public void WhenIndexAction_ThenResultIsIsAssignableFromContentResult()
{
var result = this.controller.Index();
Assert.IsAssignableFrom<ContentResult>(result);
}
[Test]
public void GivenResultIsAssignableFromContentResult_WhenIndexAction_ThenContentIsExpected()
{
var result = (ContentResult)this.controller.Index();
Assert.AreEqual("Hello World", result.Content);
}
}
See Reference documentation on UmbracoApiControllers.
:::warning This requires Umbraco version 8.4 or higher, due to a resolved issue. :::
public class ProductsController : UmbracoApiController
{
public ProductsController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext serviceContext, AppCaches appCaches, IProfilingLogger profilingLogger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, serviceContext, appCaches, profilingLogger, runtimeState, umbracoHelper, umbracoMapper) { }
public IEnumerable<string> GetAllProducts()
{
return new[] { "Table", "Chair", "Desk", "Computer", "Beer fridge" };
}
[HttpGet]
public JsonResult GetAllProductsJson()
{
return new JsonResult
{
Data = this.GetAllProducts(),
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
};
}
}
[TestFixture]
public class ProductsControllerTests : UmbracoBaseTest
{
private ProductsController controller;
[SetUp]
public override void SetUp()
{
base.SetUp();
this.controller = new ProductsController(Mock.Of<IGlobalSettings>(), Mock.Of<IUmbracoContextAccessor>(), Mock.Of<ISqlContext>(), this.ServiceContext, AppCaches.NoCache, Mock.Of<IProfilingLogger>(), Mock.Of<IRuntimeState>(), base.UmbracoHelper, base.UmbracoMapper);
}
[Test]
public void WhenGetAllProducts_ThenReturnViewModelWithExpectedProducts()
{
var expected = new[] { "Table", "Chair", "Desk", "Computer", "Beer fridge" };
var result = this.controller.GetAllProducts();
Assert.AreEqual(expected, result);
}
[Test]
public void WhenGetAllProductsJson_ThenReturnViewModelWithExpectedJson()
{
var json = JsonConvert.SerializeObject(((JsonResult)this.controller.GetAllProductsJson()).Data);
Assert.AreEqual("[\"Table\",\"Chair\",\"Desk\",\"Computer\",\"Beer fridge\"]", json);
}
}
See Core documentation on the interface ICultureDictionary.
public class HomeController : RenderMvcController
{
public HomeController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext serviceContext, AppCaches appCaches, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, serviceContext, appCaches, profilingLogger, umbracoHelper) { }
public override ActionResult Index(ContentModel model)
{
var myCustomModel = new MyCustomModel(model.Content)
{
MyProperty1 = this.Umbraco.GetDictionaryValue("myDictionaryKey")
};
return View(myCustomModel);
}
}
[TestFixture]
public class HomeControllerTests : UmbracoBaseTest
{
private HomeController controller;
[SetUp]
public override void SetUp()
{
base.SetUp();
this.controller = new HomeController(Mock.Of<IGlobalSettings>(), Mock.Of<IUmbracoContextAccessor>(), base.ServiceContext, AppCaches.NoCache, Mock.Of<IProfilingLogger>(), base.UmbracoHelper);
}
[Test]
[TestCase("myDictionaryKey", "myDictionaryValue")]
public void GivenMyDictionaryKey_WhenIndexAction_ThenReturnViewModelWithMyPropertyDictionaryValue(string key, string expected)
{
var model = new ContentModel(new Mock<IPublishedContent>().Object);
base.CultureDictionary.Setup(x => x[key]).Returns(expected);
var result = (MyCustomModel)((ViewResult)this.controller.Index(model)).Model;
Assert.AreEqual(expected, result.MyProperty1);
}
}
See Core documentation on the interface IPublishedContentQuery.
public class MyCustomController : RenderMvcController
{
public MyCustomController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext serviceContext, AppCaches appCaches, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, serviceContext, appCaches, profilingLogger, umbracoHelper) { }
public override ActionResult Index(ContentModel model)
{
var myCustomModel = new MyOtherCustomModel(model.Content)
{
OtherContent = this.Umbraco.Content(1062),
ContentAtRoot = this.Umbraco.ContentAtRoot()
};
return View(myCustomModel);
}
}
public class MyOtherCustomModel : ContentModel
{
public MyOtherCustomModel(IPublishedContent content) : base(content) { }
public IPublishedContent OtherContent { get; set; }
public IEnumerable<IPublishedContent> ContentAtRoot { get; set; }
}
[TestFixture]
public class MyCustomControllerTests : UmbracoBaseTest
{
private MyCustomController controller;
[SetUp]
public override void SetUp()
{
base.SetUp();
this.controller = new MyCustomController(Mock.Of<IGlobalSettings>(), Mock.Of<IUmbracoContextAccessor>(), base.ServiceContext, AppCaches.NoCache, Mock.Of<IProfilingLogger>(), base.UmbracoHelper);
}
[Test]
public void GivenContentQueryReturnsOtherContent_WhenIndexAction_ThenReturnViewModelWithOtherContent()
{
var currentContent = new ContentModel(new Mock<IPublishedContent>().Object);
var otherContent = Mock.Of<IPublishedContent>();
base.PublishedContentQuery.Setup(x => x.Content(1062)).Returns(otherContent);
var result = (MyOtherCustomModel)((ViewResult)this.controller.Index(currentContent)).Model;
Assert.AreEqual(otherContent, result.OtherContent);
}
[Test]
public void GivenContentQueryReturnsContentAtRoot_WhenIndexAction_ThenReturnViewModelWithContentAtRoot()
{
var currentContent = new Umbraco.Web.Models.ContentModel(new Mock<IPublishedContent>().Object);
var contentAtRoot = new List<IPublishedContent>()
{
Mock.Of<IPublishedContent>()
};
base.PublishedContentQuery.Setup(x => x.ContentAtRoot()).Returns(contentAtRoot);
var result = (MemberProfileViewModel)((ViewResult)this.controller.Index(currentContent)).Model;
Assert.AreEqual(contentAtRoot, result.ContentAtRoot);
}
}
In this example we have a controller which renders a profile page, by using Umbraco.MembershipHelper.GetCurrentMember()
.
This involves a lot of different dependencies working together behind the scenes such as the MembershipHelper
, IMemberService
, IPublishedMemberCache
and the HttpContext
which needs to be mocked for our tests to run smoothly.
public class MemberProfileController : RenderMvcController
{
public MemberProfileController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext serviceContext, AppCaches appCaches, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, serviceContext, appCaches, profilingLogger, umbracoHelper) { }
public override ActionResult Index(ContentModel model)
{
var viewModel = new MemberProfile(model.Content)
{
Member = this.Umbraco.MembershipHelper.GetCurrentMember()
};
return View(viewModel);
}
}
public class MemberProfile : ContentModel
{
public MemberProfile(IPublishedContent content) : base(content) { }
public IPublishedContent Member { get; set; }
}
[TestFixture]
public class MemberProfileControllerTests : UmbracoBaseTest
{
private MemberProfileController controller;
[SetUp]
public override void SetUp()
{
base.SetUp();
this.controller = new MemberProfileController(Mock.Of<IGlobalSettings>(), Mock.Of<IUmbracoContextAccessor>(), base.ServiceContext, AppCaches.NoCache, Mock.Of<IProfilingLogger>(), base.UmbracoHelper);
}
[Test]
[TestCase("member1")]
[TestCase("member2")]
public void GivenExistingMemberIsAuthenticated_WhenIndexAction_ThenReturnViewModelWithCurrentMember(string username)
{
var member = new Mock<IMember>();
member.Setup(x => x.Username).Returns(username);
base.memberService.Setup(x => x.GetByUsername(username)).Returns(member.Object);
var expected = Mock.Of<IPublishedContent>();
base.memberCache.Setup(x => x.GetByMember(member.Object)).Returns(expected);
var identity = new Mock<IIdentity>();
identity.Setup(user => user.IsAuthenticated).Returns(true);
identity.Setup(user => user.Name).Returns(username);
var principal = new Mock<IPrincipal>();
principal.Setup(user => user.Identity).Returns(identity.Object);
this.HttpContext.Setup(ctx => ctx.User).Returns(principal.Object);
Thread.CurrentPrincipal = principal.Object;
var actual = (MemberProfile)((ViewResult)this.controller.Index(new ContentModel(Mock.Of<IPublishedContent>()))).Model;
Assert.AreEqual(expected, actual.Member);
}
[Test]
[TestCase("member1")]
[TestCase("member2")]
public void GivenExistingMemberIsNotAuthenticated_WhenIndexAction_ThenReturnViewModelWithNullMember(string username)
{
var member = new Mock<IMember>();
member.Setup(x => x.Username).Returns(username);
base.memberService.Setup(x => x.GetByUsername(username)).Returns(member.Object);
base.memberCache.Setup(x => x.GetByMember(member.Object)).Returns(Mock.Of<IPublishedContent>());
var actual = (MemberProfile)((ViewResult)this.controller.Index(new Umbraco.Web.Models.ContentModel(Mock.Of<IPublishedContent>()))).Model;
Assert.Null(actual.Member);
}
}
See Reference documentation on Outbound Pipeline.
public class ProductPageUrlSegmentProvider : IUrlSegmentProvider
{
private readonly IUrlSegmentProvider defaultUrlSegmentProvider;
public ProductPageUrlSegmentProvider(IUrlSegmentProvider defaultUrlSegmentProvider)
{
this.defaultUrlSegmentProvider = defaultUrlSegmentProvider;
}
public string GetUrlSegment(IContentBase content, string culture = null)
{
if (content.ContentType.Alias != "productPage") return null;
var segment = defaultUrlSegmentProvider.GetUrlSegment(content, culture);
var productSku = content.GetValue(propertyTypeAlias: "productSku", culture: culture, segment: null, published: true);
return string.Format("{0}-{1}", segment, productSku);
}
}
public class RegisterCustomSegmentProviderComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Register<IUrlSegmentProvider, DefaultUrlSegmentProvider>();
composition.UrlSegmentProviders().Insert<ProductPageUrlSegmentProvider>();
}
}
[TestFixture]
public class ProductPageUrlSegmentProviderTests
{
private Mock<IUrlSegmentProvider> defaultUrlSegmentProvider;
private ProductPageUrlSegmentProvider productPageUrlSegmentProvider;
[SetUp]
public void SetUp()
{
this.defaultUrlSegmentProvider = new Mock<IUrlSegmentProvider>();
this.productPageUrlSegmentProvider = new ProductPageUrlSegmentProvider(defaultUrlSegmentProvider.Object);
}
[Test]
[TestCase("en", "swibble", "123xyz", "swibble-123xyz")]
[TestCase("en-US", "dibble", "456abc", "dibble-456abc")]
public void Given_ProductHasSku_When_GetUrlSegment_Then_ReturnExpectedUrlSegment(string culture, string defaultUrlSegment, string productSku, string expected)
{
var contentType = new Mock<ISimpleContentType>();
contentType.Setup(x => x.Alias).Returns("productPage");
var content = new Mock<IContentBase>();
content.Setup(x => x.ContentType).Returns(contentType.Object);
content.Setup(x => x.GetValue("productSku", culture, null, true)).Returns(productSku);
defaultUrlSegmentProvider.Setup(x => x.GetUrlSegment(content.Object, culture)).Returns(defaultUrlSegment);
var result = productPageUrlSegmentProvider.GetUrlSegment(content.Object, culture);
Assert.AreEqual(expected, result);
}
}