簡體   English   中英

在AreaRegistration.RegisterAllAreas()上單獨測試帶有區域的ASP.NET MVC 2路由

[英]Unit testing ASP.NET MVC 2 routes with areas bails out on AreaRegistration.RegisterAllAreas()

我在ASP.NET MVC 2中測試我的路由。我正在使用MSTest,我也在使用區域。

[TestClass]
public class RouteRegistrarTests
{
    [ClassInitialize]
    public static void ClassInitialize(TestContext testContext)
    {
        RouteTable.Routes.Clear();

        RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        RouteTable.Routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });

        AreaRegistration.RegisterAllAreas();

        routes.MapRoute(
            "default",
            "{controller}/{action}/{id}",
            new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }

    [TestMethod]
    public void RouteMaps_VerifyMappings_Match()
    {
        "~/".Route().ShouldMapTo<HomeController>(n => n.Index());
    }
}

但是,當它執行AreaRegistration.RegisterAllAreas()時,它會拋出此異常:

System.InvalidOperationException:System.InvalidOperationException:在應用程序的啟動前初始化階段,無法調用此方法。

所以,我認為我不能從我的類初始化程序中調用它。 但我什么時候可以打電話呢? 我的測試中顯然沒有Application_Start

我通過創建一個AreaRegistration類的實例並調用RegisterArea方法來解決這個問題。

例如,給定一個名為“Catalog”的區域,並使用此路徑:

public override void RegisterArea(AreaRegistrationContext context)
{
  context.MapRoute(
      "Catalog_default",
      "Catalog/{controller}/{action}/{id}",
      new {controller = "List", action = "Index", id = "" }
  );
}

這是我的測試方法:

[TestMethod]
public void TestCatalogAreaRoute()
{
  var routes = new RouteCollection();

  // Get my AreaRegistration class
  var areaRegistration = new CatalogAreaRegistration();
  Assert.AreEqual("Catalog", areaRegistration.AreaName);

  // Get an AreaRegistrationContext for my class. Give it an empty RouteCollection
  var areaRegistrationContext = new AreaRegistrationContext(areaRegistration.AreaName, routes);
  areaRegistration.RegisterArea(areaRegistrationContext);

  // Mock up an HttpContext object with my test path (using Moq)
  var context = new Mock<HttpContextBase>();
  context.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath).Returns("~/Catalog");

  // Get the RouteData based on the HttpContext
  var routeData = routes.GetRouteData(context.Object);

  Assert.IsNotNull(routeData, "Should have found the route");
  Assert.AreEqual("Catalog", routeData.DataTokens["area"]);
  Assert.AreEqual("List", routeData.Values["controller"]);
  Assert.AreEqual("Index", routeData.Values["action"]);
  Assert.AreEqual("", routeData.Values["id"]);
}

我知道我在這里遲到了,但我自己也解決了這個問題。 與Jason類似的解決方案(一次注冊一個區域),但是和我一樣,我正在使用MvcContrib.TestHelper而不是自己做嘲弄。

[TestInitialize]
public void Setup() {
    RouteTable.Routes.Clear();
    var areaReg = new AdminAreaRegistration();
    areaReg.RegisterArea(new AreaRegistrationContext(areaReg.AreaName, RouteTable.Routes));
}

[TestMethod]
public void admin_should_map_to_home() {
    "~/Admin".ShouldMapTo<HomeController>(c => c.Index());
}

請注意,MvcContrib對Rhino Mocks有很強的依賴性。 雖然我更喜歡使用Moq,但我很高興包含Rhino dll只是為了獲得這個不錯的功能。

那么在測試項目中沒有地方可以放置AreaRegistration.RegisterAllAreas(); 為了使它工作,因為它使用System.Web.Compilation.BuildManager類來編譯網站的代碼,如果在ASP.NET管道之外調用它會失敗。 我認為這是一種bug,因為它真的讓測試很難運行。

但我發明了一個2步的解決方法:)

首先,您應該修改測試項目的App.Config文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>

    </appSettings>

    <connectionStrings>

    </connectionStrings>
    <system.web>
        <compilation debug="true">
            <assemblies>
                <add assembly="!!!NAME_OF_YOUR_MVC_WEB_ASSEMBLY!!!"/>       
            </assemblies>
        </compilation>
    </system.web>
    </configuration>

Actualy你應該引用包含AreaRegistration下行程序的所有程序集。 第二,在AreaRegistration.RegisterAllAreas()之前添加這個丑陋的代碼;

typeof(BuildManager).GetProperty("PreStartInitStage", BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, 2, null);

typeof(BuildManager).GetField("_topLevelFilesCompiledStarted", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(   typeof(BuildManager).GetField("_theBuildManager", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null), true);

這僅適用於.Net 4.0及更高版本

要使AreaRegistration.RegisterAllAreas()正常工作,請首先運行以下代碼:

請注意typeof(YourMvcSiteApplication).Assembly應該是你的MVC web程序集!

    object manager = typeof(BuildManager).GetField("_theBuildManager", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
    manager.SetField("_skipTopLevelCompilationExceptions", true);
    manager.SetField("_topLevelFilesCompiledStarted", true);
    manager.SetField("_topLevelReferencedAssemblies", new List<Assembly> { typeof(YourMvcSiteApplication).Assembly });

這是實例對象的擴展方法SetField():

    public static void SetField<T>(this object source, string fieldName, T value)
    {
        var type = source.GetType();
        var info = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        if (info != null)
        {
            info.SetValue(source, value);
        }
    }

上面的代碼適用於.NET 3.5,我還沒有測試.NET 4或4.5!

這是遲了幾年,但我想我會分享。 我正在使用反射注冊所有區域。

public void RegisterAllAreas()
{
    List<AreaRegistration> objects = new List<AreaRegistration>();

    foreach (Type type in Assembly.GetAssembly(typeof(MvcApplication)).GetTypes()
            .Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(AreaRegistration))))
    {
        objects.Add((AreaRegistration)Activator.CreateInstance(type));
    }

    objects.ForEach(area => area.RegisterArea(new AreaRegistrationContext(area.AreaName, routes)));
}

這是一個很好的版本與組合方法。

代碼使用自:


[TestClass]
public class RoutesTest : RoutesTestClassBase<SomeAreaRegistration>
{
    [TestMethod]
    public void IdWithoutName()
    {
        // Area-Name is retrieved from the Registration 
        // and prepended as "~/AreaName/"

        TestRoute("Contacts/Show/0627ED05-BF19-4090-91FC-AD3865B40983", new { 
            controller = "Contacts", 
            action = "Show",
            id = "0627ED05-BF19-4090-91FC-AD3865B40983"
        });
    }

    [TestMethod]
    public void IdAndName()
    {
        TestRoute("Contacts/Show/0627ED05-BF19-4090-91FC-AD3865B40983-Some-name", new
        {
            controller = "Contacts",
            action = "Show",
            id = "0627ED05-BF19-4090-91FC-AD3865B40983",
            name= "Some-name"
        });
    }
}

基礎夾具:

public class RoutesTestClassBase<TAreaRegistration>
{
    protected void TestRoute(string url, object expectations)
    {
        var routes = new RouteCollection();
        var areaRegistration = (AreaRegistration)Activator.CreateInstance(typeof(TAreaRegistration));

        // Get an AreaRegistrationContext for my class. Give it an empty RouteCollection
        var areaRegistrationContext = new AreaRegistrationContext(areaRegistration.AreaName, routes);
        areaRegistration.RegisterArea(areaRegistrationContext);

        url = "~/" + areaRegistration.AreaName + "/" + url;

        // Mock up an HttpContext object with my test path (using Moq)
        var context = new Mock<HttpContextBase>();
        context.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath).Returns(url);

        // Get the RouteData based on the HttpContext
        var routeData = routes.GetRouteData(context.Object);

        Assert.IsNotNull(routeData, "Should have found the route");
        Assert.AreEqual(areaRegistration.AreaName, routeData.DataTokens["area"]);

        foreach (PropertyValue property in GetProperties(expectations))
        {
            Assert.IsTrue(string.Equals(property.Value.ToString(),
                routeData.Values[property.Name].ToString(),
                StringComparison.OrdinalIgnoreCase)
                , string.Format("Expected '{0}', not '{1}' for '{2}'.",
                property.Value, routeData.Values[property.Name], property.Name));
        }
    }

    private static IEnumerable<PropertyValue> GetProperties(object o)
    {
        if (o != null)
        {
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(o);
            foreach (PropertyDescriptor prop in props)
            {
                object val = prop.GetValue(o);
                if (val != null)
                {
                    yield return new PropertyValue { Name = prop.Name, Value = val };
                }
            }
        }
    }

    private sealed class PropertyValue
    {
        public string Name { get; set; }
        public object Value { get; set; }
    }
}

我想你正在尋找MVC Contrib庫中的TestHelper類。 看看MVC Contrib中的測試(它隱藏在那里)。 你會發現一切都被很好地嘲笑了.H

MVCContrib.UnitTests \\ TestHelper \\ RoutesTest.cs - 必須更新維基! 祝好運

using System.Web.Mvc;
using System.Web.Routing;
using NUnit.Framework;

namespace MVCContrib.Application.UnitTests.TestHelper
{
    /// <summary>
    /// Summary description for UserRoutesTest
    /// </summary>
    [TestFixture]
    public class UserRoutesTest
    {
        [TestFixtureSetUp]
        public void Setup()
        {
            var routes = RouteTable.Routes;
            routes.Clear();
            routes.MapRoute(
                "Default",                                              // Route name
                "{controller}",                                         // URL with parameters
                new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
                );

        }

        [Test]
        public void homeIndex()
        {
            "~/user"
                .ShouldMapTo<HomeController>(action => action.Index());
        }


        [Test]
        public void HomeShow()
        {
                         "~/home"
                           .GivenIncomingAs(HttpVerbs.Put)
                           .ShouldMapTo<HomeController>(action => action.Index());
        }

    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM