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

最後に、このように呼び出しをスタブ化してテストできます。

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

私はほとんどのテストでファクトリを使用してオブジェクトを作成しています (通常、複雑なオブジェクトの場合)。これは、Shunt を使用する 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 に答える