我在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
。
答案 0 :(得分:28)
我通过创建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"]);
}
答案 1 :(得分:11)
我知道我迟到了,但我自己也解决了这个问题。与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只是为了获得这个不错的功能。
答案 2 :(得分:10)
测试项目中没有地方可以放置AreaRegistration.RegisterAllAreas();为了使它工作,因为它使用System.Web.Compilation.BuildManager类来编译网站的代码,如果在ASP.NET管道之外调用它会失败。我认为这是一种错误,因为它真的让测试很难运行。
但我发明了一个两步的解决方法:)
首先,您应该修改测试项目的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及以上版本
答案 3 :(得分:3)
要使AreaRegistration.RegisterAllAreas()正常工作,请先运行以下代码:
请注意typeof(YourMvcSiteApplication).Assembly
应该是您的MVC网络集会的结果!!!
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!
答案 4 :(得分:3)
这已经晚了几年,但我想我会分享。我正在使用反射注册所有区域。
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)));
}
答案 5 :(得分:2)
这是一个很好的版本,结合了方法。
代码使用自:
[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; }
}
}
答案 6 :(得分:1)
我认为您正在寻找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());
}
}
}