🧩 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 mocked- RobotsTxtobject so the controller can safely cast it
- Verifying Output: Ensuring the controller returns a - ContentResultwith the expected- text/plaintype and string content
💡 Best Practices Highlighted
- Use Descriptive Test Names: - Index_ReturnsPlainText_WithExpectedContentclearly explains what the test does
- Use Shouldly for Clarity: - result.Content.ShouldBe(...)reads almost like English
- Keep 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.