2017-09-14 58 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

相關問題