如何对使用 Sitecore.Mvc.Presentation.RenderingContext 的 GlassController 操作进行单元测试

How to Unit Test a GlassController Action which Uses Sitecore.Mvc.Presentation.RenderingContext

我是一名 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);
    }
}

好的,这就是我的单元测试 class:

[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,在这种情况下它只是一个接口(它应该是具体的 class 吗?)它的数据源在返回视图之前设置为相同的值。

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

将您的代码与 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 容器注册抽象和实现,以在运行时解析依赖项。