0

我们有一些控制器操作将面包屑节点的标题更改为用户正在查看的项目的值,例如

    [MvcSiteMapNode(Title = "{0}", ParentKey = "Maintenance-Settings-Index", Key = "Maintenance-Settings-Details", PreservedRouteParameters = "id", Attributes = "{\"visibility\":\"SiteMapPathHelper,!*\"}")]
    public async Task<ActionResult> Details(int id)
    {
        var model = await GetSetting(id);
        var node = SiteMaps.Current.CurrentNode;
        if (node != null)
        {
            node.Title = string.Format("{0}", model.Name);
        }
        return View(model);
    }

这在正常查看网站时工作正常,并且表现出我们想要的样子..

但是......当尝试使用 Moq 和 FluentMVCTesting 对控制器操作进行单元测试时,我们遇到了错误。

http://www.shiningtreasures.com/post/2013/08/14/mvcsitemapprovider-4-unit-testing-with-the-sitemaps-static-methods我们添加了SiteMaps.Loader = new Mock<ISiteMapLoader>().Object;例如

创建控制器上下文

    private static ControllerContext FakeControllerContext(RouteData routeData)
    {
        var context = new Mock<HttpContextBase>();
        var request = new Mock<HttpRequestBase>();
        var response = new Mock<HttpResponseBase>();
        var session = new MockHttpSession();
        var server = new Mock<HttpServerUtilityBase>();
        context.Setup(ctx => ctx.Request).Returns(request.Object);
        context.Setup(ctx => ctx.Response).Returns(response.Object);
        context.Setup(ctx => ctx.Session).Returns(session);
        context.Setup(ctx => ctx.Server).Returns(server.Object);

        var controllerContext = new ControllerContext(context.Object, routeData ?? new RouteData(), new Mock<ControllerBase>().Object);
        return controllerContext;
    }

为每个测试初始化​​控制器

 [TestInitialize]
    public void Initialize()
    {
        var routeData = new RouteData();

        _controller = new DepartmentSettingsController
        {
            ControllerContext = FakeControllerContext(routeData)
        };
    }

然后是测试本身

[TestMethod]
    public void Details()
    {
        SiteMaps.Loader = new Mock<ISiteMapLoader>().Object;
        _controller.WithCallTo(c => c.Details(_model.Id)).ShouldRenderDefaultView()
            .WithModel<SettingViewModel>(m => m.Name == _model.Name);
    }

我们收到以下错误System.NullReferenceException:对象引用未设置为对象的实例。指的是var node = SiteMaps.Current.CurrentNode;

然后我们添加另一个测试

 [TestMethod]
    public void Edit()
    {
        SiteMaps.Loader = new Mock<ISiteMapLoader>().Object;
        _controller.WithCallTo(c => c.Edit(_model.Id)).ShouldRenderDefaultView()
            .WithModel<SettingViewModel>(m => m.Name == _model.Name);
    }

并获取MvcSiteMapProvider.MvcSiteMapException: 站点地图加载器只能在 Global.asax 的 Application_Start 事件中设置,不得再次设置。如果您使用的是外部依赖注入容器,请将 web.config 文件的 AppSettings 部分中的“MvcSiteMapProvider_UseExternalDIContainer”设置为“true”。在 MvcSiteMapProvider.SiteMaps.set_Loader(ISiteMapLoader 值)

然后SiteMaps.Loader = new Mock<ISiteMapLoader>().Object;进入测试初始化​​,例如

    [TestInitialize]
    public void Initialize()
    {
        var routeData = new RouteData();
        _controller = new DepartmentSettingsController
        {
            ControllerContext = FakeControllerContext(routeData)
        };
        SiteMaps.Loader = new Mock<ISiteMapLoader>().Object;
    }

我们得到相同的错误MvcSiteMapProvider.MvcSiteMapException: 站点地图加载器只能在 Global.asax 的 Application_Start 事件中设置,不得再次设置。如果您使用的是外部依赖注入容器,请将 web.config 文件的 AppSettings 部分中的“MvcSiteMapProvider_UseExternalDIContainer”设置为“true”。在 MvcSiteMapProvider.SiteMaps.set_Loader(ISiteMapLoader 值)

SiteMaps.Loader = new Mock<ISiteMapLoader>().Object;问题 -当您测试多个操作时,单元测试中的最佳位置在哪里

问题 - 在控制器中使用静态var node = SiteMaps.Current.CurrentNode;是最好的方法,还是有更好的方法(我们使用 Unity)

谢谢你的帮助

4

1 回答 1

1

选择

对于这个特定的用例,您根本不需要访问静态SiteMaps类。命名空间中有一个SiteMapTitle操作过滤器属性MvcSiteMapProvider.Web.Mvc.Filters,可用于根据您的模型设置标题。

[MvcSiteMapNode(Title = "{0}", ParentKey = "Maintenance-Settings-Index", Key = "Maintenance-Settings-Details", PreservedRouteParameters = "id", Attributes = "{\"visibility\":\"SiteMapPathHelper,!*\"}")]
[SiteMapTitle("Name")]
public async Task<ActionResult> Details(int id)
{
    var model = await GetSetting(id);
    return View(model);
}

模拟 ISiteMapLoader

至于设置ISiteMapLoader,我现在看到存在问题,因为它是静态的。这意味着它将在单元测试框架的运行器进程的整个生命周期中存在,无论有多少测试被设置/拆除。理想情况下,有一种方法可以读取Loader属性(或其他类似的检查)以查看它是否已被填充,如果是则跳过该步骤,但不幸的是事实并非如此。

因此,下一个最好的办法是创建一个静态帮助器类来跟踪是否ISiteMapLoader已加载,如果已加载则跳过设置操作。

public class SiteMapLoaderHelper
{
    private static ISiteMapLoader loader;

    public static void MockSiteMapLoader()
    {
        // If the loader already exists, skip setting up.
        if (loader == null)
        {
            loader = new Mock<ISiteMapLoader>().Object;
            SiteMaps.Loader = loader;
        }
    }
}

用法

 [TestInitialize]
 public void Initialize()
 {
     var routeData = new RouteData();

     _controller = new DepartmentSettingsController
     {
         ControllerContext = FakeControllerContext(routeData)
     };

     // Setup SiteMapLoader Mock
     SiteMapLoaderHelper.MockSiteMapLoader();
 }

当然,缺点是你的 mock 并不孤立于特定的单元测试,所以你对整个测试套件的所有 mock 必须在一个地方完成(假设你需要 mock 的其他成员ISiteMapLoader及其依赖项)。

另一种可能的选择

如果你愿意改变你的测试框架,还有另一种可能性。您可以将测试设置为在各自的 AppDomain 中运行,这应该允许ISiteMapLoader为每个测试卸载静态实例。

我在这个问题中发现有一个NUnit.AppDomain包可用于执行此操作。

也有人指出,XUnit在单独的 AppDomain 中自动运行单元测试,无需额外配置。

如果更改单元测试框架不是一种选择,您可以通过将与静态成员交互的每个单元测试放入单独的程序集中来解决这个问题。

MsTest 为每个测试程序集创建一个应用程序域,除非您使用 noisolation,在这种情况下没有 AppDomain 隔离。

参考:MSTest & AppDomains

于 2015-12-18T14:31:39.863 回答