🧩 Introduction
Unit testing your ASP.NET Core controllers is a critical part of delivering maintainable and reliable web applications. In this article you will learn how to write a robust unit test using xUnit, Moq and Shouldly for a custom controller called RobotsTxtController
implemented in an Umbraco application.
We will start by briefly reviewing the controller implementation then guide you step-by-step through writing a meaningful unit test that asserts expected behavior and content output.
📦 Understanding the Controller
Here's the implementation of the RobotsTxtController
:
public class RobotsTxtController : RenderController
{
public RobotsTxtController(
ILogger<RenderController> logger,
ICompositeViewEngine compositeViewEngine,
IUmbracoContextAccessor umbracoContextAccessor)
: base(logger, compositeViewEngine, umbracoContextAccessor)
{
}
[Produces("text/plain")]
public override IActionResult Index()
{
var model = (RobotsTxt)CurrentPage!;
Response.ContentType = "text/plain";
return Content(model.Content ?? string.Empty);
}
}
This controller inherits from Umbraco’s RenderController
and overrides the Index()
action to return the content of a custom RobotsTxt
model. The goal of our test is to verify that the Index()
method returns a plain text response with the correct content.
🧪 Setting Up the Unit Test
We’ll use the following testing tools:
xUnit: The test runner
Moq: For mocking dependencies
Shouldly: For clean and expressive assertions
🛠️ Prerequisites
Install the following NuGet packages:
dotnet add package xunit
dotnet add package Moq
dotnet add package Shouldly
🧰 Dependencies to Mock
The controller’s constructor has three dependencies:
ILogger<RenderController>
ICompositeViewEngine
IUmbracoContextAccessor
These will be mocked using Moq
.
🧪 Writing the Test
Here's the complete unit test for RobotsTxtController
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using Shouldly;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Web.Common.Controllers;
using Xunit;
public class RobotsTxt : PublishedElementModel
{
public RobotsTxt(IPublishedElement content) : base(content) { }
public string? Content => this.Value<string>("content");
}
public class RobotsTxtControllerTests
{
[Fact]
public void Index_ReturnsPlainText_WithExpectedContent()
{
// Arrange
var loggerMock = new Mock<ILogger<RenderController>>();
var viewEngineMock = new Mock<ICompositeViewEngine>();
var umbracoContextAccessorMock = new Mock<IUmbracoContextAccessor>();
var contentMock = new Mock<IPublishedContent>();
contentMock.Setup(c => c.Value<string>("content"))
.Returns("User-agent: *\nDisallow: /");
var model = new RobotsTxt(contentMock.Object);
var controller = new RobotsTxtController(
loggerMock.Object,
viewEngineMock.Object,
umbracoContextAccessorMock.Object
);
controller.ControllerContext = new ControllerContext();
controller.CurrentPage = model;
// Act
var result = controller.Index() as ContentResult;
// Assert
result.ShouldNotBeNull();
result.ContentType.ShouldBe("text/plain");
result.Content.ShouldBe("User-agent: *\nDisallow: /");
}
}
✅ What This Test Covers
Mocking Dependencies: Using Moq to simulate the controller’s required services and page model
Assigning
CurrentPage
: Providing a mockedRobotsTxt
object so the controller can safely cast itVerifying Output: Ensuring the controller returns a
ContentResult
with the expectedtext/plain
type and string content
💡 Best Practices Highlighted
Use Descriptive Test Names:
Index_ReturnsPlainText_WithExpectedContent
clearly explains what the test doesUse Shouldly for Clarity:
result.Content.ShouldBe(...)
reads almost like EnglishKeep Controller Testable: Design your controllers in a way that allows for easy unit testing by minimizing logic and relying on dependencies
🏁 Conclusion
By combining xUnit, Moq and Shouldly, we can write clean and expressive unit tests that ensure our controllers behave as expected. Testing your RobotsTxtController
helps you verify that SEO-critical content is delivered correctly and maintains quality over time.
Start applying this pattern to your own controllers to make your ASP.NET Core + Umbraco projects more robust and testable.