【问题标题】:How to Unit Test a GlassController Action which Uses Sitecore.Mvc.Presentation.RenderingContext如何对使用 Sitecore.Mvc.Presentation.RenderingContext 的 GlassController 动作进行单元测试
【发布时间】:2017-09-14 22:05:05
【问题描述】:

我是一名 sitecore 开发人员,我想创建一个示例 sitecore helix 单元测试项目,用于测试您在我们的“EmailArticleController”控制器的 Index() 操作方法中看到的逻辑:

using Sitecore.Mvc.Presentation;

public class EmailArticleController : GlassController
{
    //logic in below Index() method is what I want to test
    public override ActionResult Index()
    {
        var _emailArticleBusiness = new EmailArticleBusiness();
        var model = _emailArticleBusiness.FetchPopulatedModel;
        var datasourceId = RenderingContext.Current.Rendering.DataSource;
        _emailArticleBusiness.SetDataSourceID(datasourceId);

        return View("~/Views/EmailCampaign/EmailArticle.cshtml", model);
    }

    //below is alternative code I wrote for mocking and unit testing the logic in above Index() function
    private readonly IEmailArticleBusiness _businessLogic;
    private readonly RenderingContext _renderingContext;

    public EmailArticleController(IEmailArticleBusiness businessLogic, RenderingContext renderingContext)
    {
        _businessLogic = businessLogic;
        _renderingContext = renderingContext;
    }

    public ActionResult Index(int forUnitTesting)
    {
        var model = _businessLogic.FetchPopulatedModel;
        // *** do below two lines of logic somehow go into my Unit Testing class?  How?
        var datasourceId = _renderingContext.Rendering.DataSource;
        _businessLogic.SetDataSourceID(datasourceId);
        // *** 
        return View("~/Views/EmailCampaign/EmailArticle.cshtml", model);
    }
}

好的,这就是我在单元测试课上的内容:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void Test_EmailArticleController_With_RenderingContext()
    {
        //Arrange
        var businessLogicFake = new Mock<IEmailArticleBusiness>();

        var model = new EmailArticleViewModel()
        {
            ArticleControl  = new Article_Control() { },
            Metadata = new Metadata() { }
        };

        businessLogicFake.Setup(x => x.FetchPopulatedModel).Returns(model);

        // I'm not sure about the next 3 lines, how do I mock the RenderingContext and send it into the constructor, given that it needs a DataSource value too?
        var renderingContext = Mock.Of<Sitecore.Mvc.Presentation.RenderingContext>( /*what goes here, if anything?*/ ) {  /*what goes here, if anything?*/  };

        EmailArticleController controllerUnderTest = new EmailArticleController(businessLogicFake.Object, renderingContext);

        var result = controllerUnderTest.Index(3) as ViewResult;

        Assert.IsNotNull(result);
    }
}

基本上我想模拟一个渲染上下文,确保它有一个(字符串)DataSource 值设置为某个值,例如“/sitecore/home/...”,我想将它发送到控制器的构造函数中(如果这是正确的方法),调用 Index(int) 方法,同时确保我的 _businessLogic,在这种情况下它只是一个接口(它应该是具体的类吗?)将它的 dataSource 设置为相同的值在返回视图之前。

执行所有这些操作的确切代码是什么?谢谢!

【问题讨论】:

    标签: c# moq sitecore8 sitecore-mvc glass-mapper


    【解决方案1】:

    将您的代码与RenderingContext.Current.Rendering.DataSource 等静态依赖项紧密耦合可能会使孤立地测试您的代码变得困难。

    我建议您创建一个包装器来封装对RenderingContext 的静态访问。参考 GitHub 上 Glass.Mapper 存储库中的代码示例

    public interface IRenderingContext {
        string GetDataSource();
    }
    
    //...
    
    using Sitecore.Mvc.Presentation;
    
    public class RenderingContextWrapper : IRenderingContext {
        public string GetDataSource(){
            return RenderingContext.CurrentOrNull.Rendering.DataSource;
        }
    }
    

    然后您将通过构造函数注入更新您的控制器以显式依赖该抽象

    public class EmailArticleController : GlassController {
        private readonly IEmailArticleBusiness businessLogic;
        private readonly IRenderingContext renderingContext;
    
        public EmailArticleController(IEmailArticleBusiness businessLogic, IRenderingContext renderingContext) {
            this.businessLogic = businessLogic;
            this.renderingContext = renderingContext;
        }
    
        public ActionResult Index() {
            var model = businessLogic.FetchPopulatedModel;
            var datasourceId = renderingContext.GetDataSource();
            businessLogic.SetDataSourceID(datasourceId);
            return View("~/Views/EmailCampaign/EmailArticle.cshtml", model);
        }
    }
    

    您现在可以模拟所有依赖项,以便能够单独测试控制器。

    [TestClass]
    public class UnitTest1 {
        [TestMethod]
        public void Test_EmailArticleController_With_RenderingContext() {
            //Arrange
            var businessLogicFake = new Mock<IEmailArticleBusiness>();
    
            var model = new EmailArticleViewModel() {
                ArticleControl  = new Article_Control() { },
                Metadata = new Metadata() { }
            };
    
            businessLogicFake.Setup(x => x.FetchPopulatedModel).Returns(model);
    
            var datasourceId = "fake_datasourceId";
            var renderingContext = Mock.Of<IRenderingContext>(_ => _.GetDataSource() == datasourceId);
    
            var controllerUnderTest = new EmailArticleController(businessLogicFake.Object, renderingContext);
    
            //Act
            var result = controllerUnderTest.Index() as ViewResult;
    
            //Assert
            Assert.IsNotNull(result);
            businessLogicFake.Verify(_ => _.SetDataSourceID(datasourceId), Times.AtLeastOnce());
        }
    }
    

    您的生产代码显然会向您的 DI 容器注册抽象和实现,以便在运行时解析依赖项。

    【讨论】:

    • 非常非常感谢您的回答!!它就像一个魅力!您说“将代码与 RenderingContext.Current.Rendering.DataSource 等静态依赖项紧密耦合会使孤立地测试代码变得困难。最好创建一个包装器来封装对 RenderingContext 的静态访问。”是否有任何其他原因为什么我们必须向 EmailArticleController() 类添加代码才能对其进行单元测试?您的意思是我们无法在不向构造函数中发送任何内容的情况下对原始 Index() 方法进行单元测试,对吗?
    • @user3034243 更多的是设计问题。您无法控制该静态依赖项,这意味着您也无法控制在正常操作之外需要时如何初始化它。正是由于对您不拥有的代码缺乏控制,所以很难单独进行测试。阅读 SOLID 等主题,您将更好地了解它与设计易于维护的干净代码之间的关系,其中还包括测试。
    • @user3034243 我几乎可以肯定可能还有另一种可能的方法,但是当您可以轻松地从一开始就正确设计它时,我会选择更简洁的设计。
    • 一如既往地感谢您出色而彻底的回答!我将研究另一种方法。同时,您介意在stackoverflow.com/questions/46247255/… 看看我的问题吗?谢谢!
    • @user3034243 我看了一下,和这个基本一样。您不断将代码耦合到静态类。您应该真正检查存储库,看看它们是否具有可以让您更好地设计代码的抽象。
    猜你喜欢
    • 2018-02-25
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 2012-01-08
    • 2017-05-28
    • 2013-04-16
    • 2016-05-29
    • 2017-10-04
    相关资源
    最近更新 更多