AI agents: the content index is at /llms.txt. Every page is also available as Markdown — send the request header Accept: text/markdown, or append .md to the page URL.
Xperience Docs

Cookie consent

We use necessary cookies (opens in a new tab) to run our website and improve your experience while browsing. Additional cookies are only used with your consent. You may revoke your consent on the Cookies Policy (opens in a new tab) page or in your browser at any time.

Configure (opens in a new tab)
  • Documentation
    InstallationUsing the Xperience interfaceDeveloping websitesConfiguring XperienceManaging usersSecuring websitesDeploying websitesManaging website contentMultilingual websitesMacro expressionsExternal utilitiesIntegrating 3rd party systemsCustom developmentE-commerce featuresOn-line marketing featuresRelease notes - Xperience 13Xperience terminology glossary
  • Tutorial
    Using the Xperience interfaceDeveloper tutorialBusiness tutorialResource hub for marketers
  • API Examples
    ConfigurationContent managementDevelopmentE-commerceOn-line marketingSocial media
  1. Home
  2. Custom development
  3. Writing automated tests
  4. Testing MVC controllers

Testing MVC controllers


Was this page helpful?

Send us feedback Go back to top
  • Community Questions & Answers (opens in a new tab)
  • Contact support (opens in a new tab)

You can test the logic of your MVC controllers that work with content items, such as articles or products, info objects as well as any other objects. This is done by providing fake representations of these objects. Using this approach, you can create the necessary objects without accessing the database.

The recommended approach for providing fake objects is to use repositories (adapters) that, for content items, make use of generated classes. Repositories provide the following advantages over using DocumentQuery calls directly in controllers:

  • Your tests will work regardless of the used methods. This is important as not all document query methods are supported when creating fake objects.
  • Repositories only need to contain the methods the application really needs. This allows you to make changes to the implementation in a much smaller scale than if you were using document query.

We also recommend writing individual controllers to use dependency injection and retrieve their dependencies via their constructors. This approach allows you to create classes with clearly defined responsibilities, which are, in turn easier to test.

You can also use containers such as Autofac⁠ (opens in a new tab) (or similar container implementations) to simplify the process of creating controllers and their dependencies, as well as handling the life cycle of the dependencies.

See also: Initializing Xperience services with dependency injection

See the project code of the Dancing Goat MVC sample site for a reference on how to implement tests for your controllers.

Providing content items in controller tests

The following example demonstrates the implementation of a sample ArticleController controller and corresponding ArticleControllerTests. The controller displays article details. Note that the sample implementation is dependent on external libraries, such as Autofac⁠ (opens in a new tab), TestStack.FluentMVCTesting⁠ (opens in a new tab), and NSubstitute⁠ (opens in a new tab).

  1. Create a repository that provides access to content items using generated classes. The repository only needs to contain the methods required by the application, not the data layer.

    C#
    public interface IArticleRepository
    {
        IEnumerable<Article> GetArticle(int documentId);
    
        ...
    }
    C#
    public sealed class ArticleRepository : IArticleRepository
    ...
        public Article GetArticle(int documentId)
        {
            return new DocumentQuery<Article>()
                            .WithId(documentId)
                            .Culture(cultureCode)
                            .OnSite(siteName)
                            .FirstOrDefault();
        }
    ...
  2. Register the repository in your dependency injection provider or container. For example, in the application's Startup.cs or Global.asax.cs file.

    C#
    builder.Register<ArticleRepository>().As<IArticleRepository>();
  3. Inject the repository and any other dependencies in the controller constructor.

    C#
    public class ArticleController : Controller
    {
        private readonly IArticleRepository articleRepository;
    
        public ArticleController(IArticleRepository repository)
        {
            articleRepository = repository;
        }
    ...
  4. The controller contains an action that returns a view based on a specified article's ID, or HttpNotFoundResult if the article does not exist.

    C#
    // GET: Articles/Show/{id}
    public ActionResult Show(int articleId)
    {
        var article = articleRepository.GetArticle(articleId);
        if (article == null)
        {
            return HttpNotFound();
        }
        return View(article);
    }
  5. Set up the controller test and create the controller with the required dependencies. The following example uses the NUnit⁠ (opens in a new tab) testing framework.

    C#
    private IArticleRepository articleRepository;
    private ArticleController controller;
    
    [SetUp]
    public void SetUp()
    {
        // Allows creating of articles without accessing the database
        Fake().DocumentType<Article>(Article.CLASS_NAME);
    
        // Creates a mock article repository
        var article = TreeNode.New<Article>()
                                    .With(a => a.Fields.Title = "TestArticle")
                                    .With(a => a.SetValue("DocumentID", 1));
    
        articleRepository = Substitute.For<IArticleRepository>();
        controller = new ArticleController(articleRepository);
        articleRepository.GetArticle(1).Returns(article);
    }
  6. Create individual test cases for the controller.

    C#
    [Test]
    public void Show_WithoutExistingArticle_ReturnsHttpNotFoundStatusCode()
    {
        controller.WithCallTo(c => c.Show(2))
            .ShouldGiveHttpStatus(HttpStatusCode.NotFound);
    }