Unit Testing an ASP.NET Core Controller Using xUnit, Moq and Shouldly

How to Write a Clean and Effective Unit Test for a Custom Umbraco Controller

Posted by Hüseyin Sekmenoğlu on July 05, 2023 Backend Development Testing Tooling & Productivity

🧩 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 RobotsTxt object so the controller can safely cast it

  • Verifying Output: Ensuring the controller returns a ContentResult with the expected text/plain type and string content


💡 Best Practices Highlighted

  • Use Descriptive Test Names: Index_ReturnsPlainText_WithExpectedContent clearly 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.