2010-04-26 18 views
20

Estoy probando unitarias mis rutas en ASP.NET MVC 2. Estoy usando MSTest y estoy usando áreas también.Pruebas unitarias ASP.NET MVC 2 rutas con áreas resguardadas en AreaRegistration.RegisterAllAreas()

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

Cuando se ejecuta AreaRegistration.RegisterAllAreas() sin embargo, se lanza esta excepción:

System.InvalidOperationException: System.InvalidOperationException: Este método no puede ser llamado durante la fase de inicialización previa al arranque de la aplicación.

Por lo tanto, creo que no puedo llamarlo desde mi inicializador de clase. Pero cuando puede lo llamo? Obviamente no tengo un Application_Start en mi prueba.

+0

¿Cuál es el propósito del parámetro 'testContext'? Nunca se usa. – MEMark

+0

@MEMark - se requiere para [ClassInitialize] – Swati

+0

@Swati Por supuesto. Simplemente no he usado MSTest en mucho tiempo. – MEMark

Respuesta

29

He resuelto mediante la creación de una instancia de mi clase AreaRegistration y llamando al método RegisterArea.

Por ejemplo, dada una zona denominada "Catálogo" con esta ruta:

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

Este es mi método de prueba:

[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"]); 
} 
+0

Supongo que es una buena solución, gracias. –

+0

No pude hacer que esto funcione en mi área de 'Administración'. routeData devuelve nulo. ¿Podría MVC 4 ser lo suficientemente diferente como para causar esto? Avanzo un poco más con mi accesorio de prueba original, que es muy similar, pero no coincide en la ruta del área y en su lugar se ajusta a la ruta predeterminada. –

+0

Aha! Lo descubrí ... No configuré el controlador predeterminado cuando registré mi área (que es la predeterminada de la plantilla). Además 1. ¡Gracias! –

10

Bueno, no hay lugar en el proyecto de prueba que puede poner AreaRegistration.RegisterAllAreas(); para hacer que funcione, porque usa la clase System.Web.Compilation.BuildManager para compilar código para el sitio web, y falla si se llama fuera de la canalización de ASP.NET. Creo que es una especie de error, porque realmente hace que las pruebas sean muy difíciles de ejecutar.

Pero he inventado una solución de 2 pasos :)

En primer lugar debe modificar el archivo app.config del proyecto de prueba

<?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> 

podía comprender que debe refference todos los conjuntos que contienen trazos bajos AreaRegistration. En segundo lugar agregue este código feo antes de 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); 

esto sólo funciona para .Net 4.0 y superior

+0

Gracias. Aunque no es muy bonito, hace que pase mi prueba. Sin embargo, encontré que falla al depurar la prueba, arrojando una excepción diferente (algo sobre un nombre de archivo que contiene un carácter ilegal). De todos modos, aceptaré tu respuesta por ahora. Todavía estoy esperando una solución adecuada. –

+0

Woks bien! ¡Qué hackeo feo, aunque ... –

+0

Lifesaver, cheers! –

1

Creo que busca la clase TestHelper en la biblioteca MVC Contrib. Eche un vistazo a las pruebas en MVC Contrib (está escondido allí). Encontrará que todo está muy bien burlado. H

MVCContrib.UnitTests\TestHelper\RoutesTest.cs - ¡debe actualizar la wiki! Buena suerte

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

    } 
} 
+1

Como puede ver en mi ejemplo de código, ya estoy usando el asistente de prueba MvcContrib (la llamada al método de extensión 'ShouldMapTo'). No parece haber soporte específico para áreas en MvcContrib en este momento o no lo han hecho evidente. Incluso si hay suficiente soporte para las áreas de prueba, todavía tengo el problema de que mi código de registro de ruta arroja esta excepción cuando se llama desde una prueba unitaria debido a la llamada 'AreaRegistration.RegisterAllAreas()'. –

11

Sé que estoy sonando en la tarde aquí, pero yo Acabo de solucionar este problema yo mismo. Solución similar a Jason (registre un área a la vez), pero como usted, estoy usando MvcContrib.TestHelper en lugar de hacer mi propia burla.

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

Tenga en cuenta que MvcContrib tiene una gran dependencia de Rhino Mocks. Si bien prefiero usar Moq, estoy de acuerdo con incluir el Rhino dll solo para obtener esta buena funcionalidad.

+0

+1 Nunca es demasiado tarde para obtener una buena respuesta – StarTrekRedneck

+1

Desafortunadamente, MvcContrib no tiene soporte MVC 4 o MVC 5, a menos que lo compile usted mismo. Aparece un error porque el tipo de controlador no coincide con el que se compiló en ShouldMapTo (). Gracias, pero por ahora esto es demasiada molestia. – NightOwl888

2

Aquí una buena versión con enfoques combinados.

código utilizado a partir de:


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

La base-accesorio:

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

Para Hacer AreaRegistration.RegisterAllAreas() de trabajo, ejecute el siguiente código primero:

Atención: typeof(YourMvcSiteApplication).Assembly debe ser resultado en su conjunto de banda 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 }); 

Aquí es el método de extensión SetField() de un objeto de instancia:

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

Los códigos anteriores funciona para .NET 3.5, no tengo la prueba de .NET 4 o 4.5 todavía!

3

Esto es un par de años tarde, pero pensé que lo compartiría. Estoy registrando todas las áreas usando reflexión.

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