2

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

最后,我可以通过 stubing 调用来测试它,就像这样。

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

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

4

1 回答 1

1

我终于用下面的方法做到了。

这是我想测试的 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()
        {
        }
    }
}
于 2013-04-05T10:05:24.333 回答