2013-03-21 37 views
0

我正在爲ASP.NET MVC 4編寫一個自定義的RazorViewEngine,並且我很難單元測試它,因爲基類正在調用那些上傳到BuildManager和VirtualPathProvider的東西拋出所有類型的異常,所以在我做的每一個新測試中,我都需要去除其他東西,因爲底層機制正在調用我沒有存根的對象,並拋出「對象引用未設置爲對象的實例。 「RazorViewEngine和單元測試

所以我所做的就是編寫一個類似於這個接口的接口,允許我將調用包裝到引擎的基類中。

internal interface IViewEngineDelegate 
{ 
    Func<ControllerContext, string, IView> CreatePartialView { get; set; } 

    Func<ControllerContext, string, string, IView> CreateView { get; set; } 

    Func<ControllerContext, string, bool> FileExists { get; set; } 

    Func<ControllerContext, string, bool, ViewEngineResult> FindPartialView { get; set; } 

    Func<ControllerContext, string, string, bool, ViewEngineResult> FindView { get; set; } 
} 

現在,我可以在我的生產代碼中執行以下操作。

public class CsEmbeddedRazorViewEngine : RazorViewEngine 
{ 
    private readonly IViewEngineDelegate _viewEngineDelegate; 

    public CsEmbeddedRazorViewEngine() 
    { 
     _viewEngineDelegate = new ViewEngineDelegate 
     { 
      CreatePartialView = base.CreatePartialView, 
      CreateView = base.CreateView, 
      FileExists = base.FileExists, 
      FindPartialView = base.FindPartialView, 
      FindView = base.FindView 
     }; 
    } 

    internal CsEmbeddedRazorViewEngine(IViewEngineDelegate viewEngineDelegate) 
    { 
     _viewEngineDelegate = viewEngineDelegate; 
    } 

    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) 
    { 
     // TODO: Do something. 

     return _viewEngineDelegate.CreatePartialView(controllerContext, partialPath) 
    } 
} 

最後,我可以測試它通過殘留電話,像這樣。

ViewEngineDelegate engineDelegate = new ViewEngineDelegate 
{ 
    CreatePartialView = (controllerContext, partialPath) => FakeViewFactory.Instance.Create(controllerContext, partialPath), 
}; 

CsEmbeddedRazorViewEngine engine = new CsEmbeddedRazorViewEngine(engineDelegate); 

經過一番思考我想到的只是這樣做,因爲我認爲我在工程設計,所以我決定去一個更簡單的方法。

public class CsEmbeddedRazorViewEngine : RazorViewEngine 
{ 
    protected sealed override IView CreatePartialView(ControllerContext controllerContext, string partialPath) 
    { 
     // TODO: Do something. 

     return default(IView); 
    } 

    protected virtual IView CreatePartialViewCore(ControllerContext controllerContext, string partialPath) 
    { 
     return base.CreatePartialView(controllerContext, partialPath); 
    } 
} 

我不是很滿意,這兩種方法中的一種,這就是我張貼關於它的原因,我不知道是否有這樣做,或者還有更好的辦法,也許這只是我,這些都是可接受/合理的方法。

回答

0

我終於用下面的方法做了。

這是我想測試的ViewEngine的一個示例。

public class CsEmbeddedRazorViewEngine : RazorViewEngine 
{ 
    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) 
    { 
     viewPath = GetViewPath(controllerContext, viewPath); 

     masterPath = GetViewPath(controllerContext, masterPath); 

     return base.CreateView(controllerContext, viewPath, masterPath); 
    } 

    private static string GetAssemblyName(ControllerContext controllerContext) 
    { 
     return Path.GetFileNameWithoutExtension(controllerContext.Controller.GetType().Assembly.Location); 
    } 

    private static string GetViewPath(ControllerContext controllerContext, string virtualPath) 
    { 
     string asmName = GetAssemblyName(controllerContext); 

     return virtualPath.Replace("%Assembly%", asmName); 
    } 
} 

我使用的是工廠的大部分我的測試中創造我的對象(通常,對於複雜的對象),所以這是工廠負責創建一個使用一個分流器(見「自並勵的視圖引擎'更多信息的測試模式)。

public sealed class CsEmbeddedRazorViewEngineFactory : SingleFactory<CsEmbeddedRazorViewEngineFactory> 
{ 
    public CsEmbeddedRazorViewEngine Create(bool fileExists) 
    { 
     return new CsEmbeddedRazorViewEngineShunt(fileExists); 
    } 

    private class CsEmbeddedRazorViewEngineShunt : CsEmbeddedRazorViewEngine 
    { 
     private readonly bool _fileExists; 

     private readonly IViewEngine _viewEngine; 

     public CsEmbeddedRazorViewEngineShunt(bool fileExists) 
     { 
      _fileExists = fileExists; 

      _viewEngine = FakeViewEngineFactory.Instance.Create(); 
     } 

     public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) 
     { 
      IView view = CreateView(controllerContext, viewName, masterName); 

      return new ViewEngineResult(view, _viewEngine); 
     } 

     public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) 
     { 
      IView view = CreatePartialView(controllerContext, partialViewName); 

      return new ViewEngineResult(view, _viewEngine); 
     } 

     protected override bool FileExists(ControllerContext controllerContext, string virtualPath) 
     { 
      return _fileExists; 
     } 
    } 
} 

這是我爲ViewEngine所做的實際測試。

internal class CsEmbeddedRazorViewEngineTests 
{ 
    public class FindView 
    { 
     [Theory, 
     InlineData("~/%Assembly%/Views/{1}/{0}.cshtml", "~/Lynx.Tests.Framework.Web.Mvc/Views/{1}/{0}.cshtml"), 
     InlineData("~/%Assembly%/Areas/{2}/Views/{1}/{0}.cshtml", "~/Lynx.Tests.Framework.Web.Mvc/Areas/{2}/Views/{1}/{0}.cshtml")] 
     public void Should_prefix_the_virtual_path_with_the_assembly_name_for_normal_views(string viewPath, string expectedViewPath) 
     { 
      // Arrange 
      const bool FILE_EXISTS = true; 

      CsEmbeddedRazorViewEngine engine = CsEmbeddedRazorViewEngineFactory.Instance.Create(FILE_EXISTS); 

      ControllerBase controller = new ControllerStub(); 

      ControllerContext controllerContext = FakeControllerContextFactory.Instance.Create(controller); 

      // Act 
      ViewEngineResult result = engine.FindView(controllerContext, viewPath, string.Empty, false); 

      RazorView razorView = (RazorView)result.View; 

      string actualViewPath = razorView.ViewPath; 

      // Assert 
      actualViewPath.Should().Be(expectedViewPath); 
     } 

     [Theory, 
     InlineData(@"Views\DummyView.cshtml", "~/%Assembly%/Views/Shared/{0}.cshtml", "~/Lynx.Tests.Framework.Web.Mvc/Views/Shared/{0}.cshtml"), 
     InlineData(@"Views\DummyView.cshtml", "~/%Assembly%/Areas/{2}/Views/Shared/{0}.cshtml", "~/Lynx.Tests.Framework.Web.Mvc/Areas/{2}/Views/Shared/{0}.cshtml")] 
     public void Should_prefix_the_virtual_path_with_the_assembly_name_for_layout(string viewPath, string layoutPath, string expectedLayoutPath) 
     { 
      // Arrange 
      const bool FILE_EXISTS = true; 

      CsEmbeddedRazorViewEngine engine = CsEmbeddedRazorViewEngineFactory.Instance.Create(FILE_EXISTS); 

      ControllerBase controller = new ControllerStub(); 

      ControllerContext controllerContext = FakeControllerContextFactory.Instance.Create(controller); 

      // Act 
      ViewEngineResult result = engine.FindView(controllerContext, viewPath, layoutPath, false); 

      RazorView razorView = (RazorView)result.View; 

      string actualLayoutPath = razorView.LayoutPath; 

      // Assert 
      actualLayoutPath.Should().Be(expectedLayoutPath); 
     } 
    } 

    private class ControllerStub : ControllerBase 
    { 
     protected override void ExecuteCore() 
     { 
     } 
    } 
}