[英]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.