RazorViewEngine和单元测试

时间:2013-03-21 13:01:11

标签: unit-testing razor asp.net-mvc-4

我正在为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);
    }
}

我对这些方法中的任何一种都不满意,这就是我发布它的原因,我想知道是否有更好的方法可以做到这一点或者好,也许这只是我,这些是可接受的/合理的方法

1 个答案:

答案 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()
        {
        }
    }
}