2017-09-14 62 views
1

我是一名sitecore开发人员,我想创建一个示例sitecore helix单元测试项目,用于测试您在我们的“EmailArticleController”控制器中看到的逻辑指数()操作方法:如何单元测试使用Sitecore.Mvc.Presentation.RenderingContext的GlassController动作

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); 
    } 
} 

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

做所有这些的确切代码是什么?谢谢!

回答

1

将代码紧密耦合到静态依赖关系(如RenderingContext.Current.Rendering.DataSource)可以使测试代码非常困难。

我建议你创建一个封装来封装对RenderingContext的静态访问。参照在Glass.Mapper库GitHub上

发现
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容器的抽象和实现注册到运行时解析依赖关系。

+0

非常感谢你的回答!它像一个魅力工作!你说过:“将代码紧密结合到像RenderingContext.Current.Rendering.DataSource这样的静态依赖关系可以使测试代码变得困难,最好创建一个封装来封装对RenderingContext的静态访问。” 为什么我们绝对必须向EmailArticleController()类添加代码才能进行单元测试,还有其他原因吗?你暗示我们不可能在没有向构造函数中发送任何东西的情况下对原始的Index()方法进行单元测试,对吗? – user3034243

+1

@ user3034243它更多的是设计问题。您无法控制该静态依赖关系,这意味着您无法控制在正常操作之外如何进行初始化。缺乏对不拥有的代码的控制,使得单独测试变得困难。阅读SOLID这样的主题,您将会更好地理解它与设计易于维护的清洁代码之间的关系,其中还包括测试。 – Nkosi

+0

@ user3034243我几乎可以肯定,可能有另一种可能的方法,但是当你可以轻松地设计它时,我会每次选择更清洁的设计。 – Nkosi

相关问题