2009-06-04 15 views
12

che sto cercando di scrivere un test per un extensionmethod UrlHelper che viene utilizzato in questo modo:unit testing Url.Action (usando Rhino Mocks?)

Url.Action<TestController>(x => x.TestAction()); 

Tuttavia, io non riesco configurarlo correttamente in modo che posso creare un nuovo UrlHelper e quindi asserire che l'URL restituito era quello previsto. Questo è quello che ho, ma sono aperto a tutto ciò che non comporta anche il beffeggiamento. ; O)

 [Test] 
    public void Should_return_Test_slash_TestAction() 
    { 
     // Arrange 
     RouteTable.Routes.Add("TestRoute", new Route("{controller}/{action}", new MvcRouteHandler())); 
     var mocks = new MockRepository(); 
     var context = mocks.FakeHttpContext(); // the extension from hanselman 
     var helper = new UrlHelper(new RequestContext(context, new RouteData()), RouteTable.Routes); 

     // Act 
     var result = helper.Action<TestController>(x => x.TestAction()); 

     // Assert 
     Assert.That(result, Is.EqualTo("Test/TestAction")); 
    } 

Ho provato a cambiare a urlHelper.Action ("Test", "TestAction"), ma fallirà comunque così so che non è il mio extensionmethod che non funziona. NUnit ritorna:

NUnit.Framework.AssertionException: Expected string length 15 but was 0. Strings differ at index 0. 
Expected: "Test/TestAction" 
But was: <string.Empty> 

Ho verificato che il percorso è stato registrato e di lavoro e sto usando estensione Hanselmans per la creazione di un HttpContext falso. Ecco ciò che il mio UrlHelper extentionmethod assomigliare:

 public static string Action<TController>(this UrlHelper urlHelper, Expression<Func<TController, object>> actionExpression) where TController : Controller 
    { 
     var controllerName = typeof(TController).GetControllerName(); 
     var actionName = actionExpression.GetActionName(); 

     return urlHelper.Action(actionName, controllerName); 
    } 

    public static string GetControllerName(this Type controllerType) 
    { 
     return controllerType.Name.Replace("Controller", string.Empty); 
    } 

    public static string GetActionName(this LambdaExpression actionExpression) 
    { 
     return ((MethodCallExpression)actionExpression.Body).Method.Name; 
    } 

Tutte le idee su ciò che mi manca per farlo funzionare ??? /Kristoffer

+0

Puoi pubblicare il tuo codice metodo Factory.CreateUrlHelper? – nkirkes

risposta

11

Il motivo per cui non funziona è che internamente l'oggetto RouteCollection chiama il metodo ApplyAppPathModifier su HttpResponseBase.Sembra che il codice mock di Hanselman non abbia alcuna aspettativa su quel metodo, quindi restituisce null, motivo per cui tutte le chiamate al metodo Action su UrlHelper restituiscono una stringa vuota. La soluzione sarebbe quella di impostare un'aspettativa sul metodo ApplyAppPathModifier del mock HttpResponseBase per restituire semplicemente il valore che è passato in esso. Non sono un esperto di Rhino Mock quindi non sono completamente sicuro della sintassi. Se si utilizza Moq, allora sarebbe simile a questa:

httpResponse.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>())) 
    .Returns((string s) => s); 

Oppure, se si utilizza un mock arrotolato a mano, qualcosa di simile a questo dovrebbe funzionare:

internal class FakeHttpContext : HttpContextBase 
{ 
    private HttpRequestBase _request; 
    private HttpResponseBase _response; 

    public FakeHttpContext() 
    { 
     _request = new FakeHttpRequest(); 
     _response = new FakeHttpResponse(); 
    } 

    public override HttpRequestBase Request 
    { 
     get { return _request; } 
    } 

    public override HttpResponseBase Response 
    { 
     get { return _response; } 
    } 
} 

internal class FakeHttpResponse : HttpResponseBase 
{ 
    public override string ApplyAppPathModifier(string virtualPath) 
    { 
     return virtualPath; 
    } 
} 

internal class FakeHttpRequest : HttpRequestBase 
{ 
    private NameValueCollection _serverVariables = new NameValueCollection(); 

    public override string ApplicationPath 
    { 
     get { return "/"; } 
    } 

    public override NameValueCollection ServerVariables 
    { 
     get { return _serverVariables; } 
    } 
} 

Il codice di cui sopra dovrebbe essere l'implementazione minima necessaria di HttpContextBase per fare un passaggio di prova unitario per UrlHelper. L'ho provato e ha funzionato. Spero che questo ti aiuti.

1

So che questo non risponde direttamente alla tua domanda, ma c'è un motivo per cui stai cercando di scrivere il tuo metodo di estensione generico invece di usare quello che è disponibile nell'assembly MVC Futures? (Microsoft.Web.Mvc.dll) Oppure stai provando a testare l'estensione del metodo msft?

[Modifica 1] Scusa, stavo pensando all'estensione helper Html in Futures.

Nel frattempo, proverò la mia mano a un test di unità per vedere se ottengo lo stesso risultato.

[Modifica 2] Ok, quindi questo non è ancora completamente funzionante, ma non fa esplodere. Il risultato è semplicemente restituire una stringa vuota. Ho preso un po MVC beffardo aiutanti da Scott Hanselman a this link.

Ho anche creato un metodo Url.Action<TController>, insieme a metodi di supporto ho strappato dalla fonte MVC:

public static string Action<TController>(this UrlHelper helper, Expression<Action<TController>> action) where TController : Controller 
{ 
    string result = BuildUrlFromExpression<TController>(helper.RequestContext, helper.RouteCollection, action); 
    return result; 
} 

public static string BuildUrlFromExpression<TController>(RequestContext context, RouteCollection routeCollection, Expression<Action<TController>> action) where TController : Controller 
{ 
    RouteValueDictionary routeValuesFromExpression = GetRouteValuesFromExpression<TController>(action); 
    VirtualPathData virtualPath = routeCollection.GetVirtualPath(context, routeValuesFromExpression); 
    if (virtualPath != null) 
    { 
     return virtualPath.VirtualPath; 
    } 
    return null; 
} 

public static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action) where TController : Controller 
{ 
    if (action == null) 
    { 
     throw new ArgumentNullException("action"); 
    } 
    MethodCallExpression body = action.Body as MethodCallExpression; 
    if (body == null) 
    { 
     throw new ArgumentException("MvcResources.ExpressionHelper_MustBeMethodCall", "action"); 
    } 
    string name = typeof(TController).Name; 
    if (!name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)) 
    { 
     throw new ArgumentException("MvcResources.ExpressionHelper_TargetMustEndInController", "action"); 
    } 
    name = name.Substring(0, name.Length - "Controller".Length); 
    if (name.Length == 0) 
    { 
     throw new ArgumentException("MvcResources.ExpressionHelper_CannotRouteToController", "action"); 
    } 
    RouteValueDictionary rvd = new RouteValueDictionary(); 
    rvd.Add("Controller", name); 
    rvd.Add("Action", body.Method.Name); 
    AddParameterValuesFromExpressionToDictionary(rvd, body); 
    return rvd; 
} 

private static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd, MethodCallExpression call) 
{ 
    ParameterInfo[] parameters = call.Method.GetParameters(); 
    if (parameters.Length > 0) 
    { 
     for (int i = 0; i < parameters.Length; i++) 
     { 
      Expression expression = call.Arguments[i]; 
      object obj2 = null; 
      ConstantExpression expression2 = expression as ConstantExpression; 
      if (expression2 != null) 
      { 
       obj2 = expression2.Value; 
      } 
      else 
      { 
       Expression<Func<object>> expression3 = Expression.Lambda<Func<object>>(Expression.Convert(expression, typeof(object)), new ParameterExpression[0]); 
       obj2 = expression3.Compile()(); 
      } 
      rvd.Add(parameters[i].Name, obj2); 
     } 
    } 
} 

E, infine, ecco la prova che sto correndo :

[Test] 
    public void GenericActionLinkHelperTest() 
    { 
     RouteRegistrar.RegisterRoutesTo(RouteTable.Routes); 

     var mocks = new MockRepository(); 
     var context = mocks.FakeHttpContext(); // the extension from hanselman 

     var helper = new UrlHelper(new RequestContext(context, new RouteData()), RouteTable.Routes); 
     string result = helper.Action<ProjectsController>(x => x.Index()); 

     // currently outputs an empty string, so something is fudded up. 
     Console.WriteLine(result); 
    } 

Non so ancora il motivo per cui l'uscita è una stringa vuota, ma terrò scherzi con questo come ho tempo. Sarei curioso di sapere se nel frattempo trovi una soluzione.

+0

Ho aggiornato i miei esempi e ora ottengo lo stesso risultato e credo che si possa chiamare questo progresso. Ancora non sono sicuro del motivo per cui ottengo una stringa vuota perché ho verificato che il mio percorso funziona e corrisponde a "~/Test/TestAction". –

+0

Sì, non sono sicuro del perché sia ​​tornato vuoto. All'inizio ho pensato che fosse perché nel progetto ho eseguito questo test, ho alcune strade strane e forse non è riuscito a trovare una corrispondenza. Dato che stai ottenendo lo stesso risultato, non sono sicuro che si tratti di un problema di routing. Giocherò ancora un po '. – nkirkes

2

sono stato in grado di testare il metodo BuildUrlFromExpression, ma avevo bisogno di registrare i miei RouteTable.Routes prima di eseguire i test:

[ClassInitialize] 
public static void FixtureSetUp(TestContext @__testContext) 
{ 
    MvcApplication.RegisterRoutes(RouteTable.Routes); 
} 

Poi spegnere/setup queste proprietà:

HttpRequestBase request = mocks.PartialMock<HttpRequestBase>(); 
request.Stub(r => r.ApplicationPath).Return(string.Empty); 

HttpResponseBase response = mocks.PartialMock<HttpResponseBase>(); 
SetupResult.For(response.ApplyAppPathModifier(Arg<String>.Is.Anything)).IgnoreArguments().Do((Func<string, string>)((arg) => { return arg; })); 

Dopo che il metodo BuildUrlFromExpression ha restituito uls come previsto.