如何单元测试使用Sitecore.Mvc.Presentation.RenderingContext的GlassController动作

时间:2017-09-14 22:05:06

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

我是sitecore开发人员,我想创建一个示例sitecore螺旋单元测试项目,用于测试您在“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设置为相同的值在返回视图之前。

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

1 个答案:

答案 0 :(得分: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容器注册抽象和实现,以便运行时解析依赖项。