我正在为ASP.NET MVC 4编写一个自定义的RazorViewEngine,我很难对它进行单元测试,因为基类调用的是构建BuildManager和VirtualPathProvider的东西,它们会抛出所有类型的例外,所以在我做的每一个新测试中,我都需要删除其他东西,因为底层机制是调用我没有存根的对象并抛出“对象引用未设置为对象的实例。”
所以我所做的就是编写一个类似于这个的接口,它允许我将调用包装到引擎的基类。
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 :(得分: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);
}
}
我在大多数测试中使用工厂来创建我的对象(通常用于复杂对象),因此这是负责创建使用分流器的ViewEngine的工厂(请参阅“自分流”测试模式更多信息)。
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()
{
}
}
}