2009-06-04 7 views
12

Ich versuche, einen Test für eine UrlHelper Extension zu schreiben, die wie folgt verwendet:Unittesting Url.Action (mit Rhino Mocks?)

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

Allerdings kann ich es nicht richtig so gesetzt scheinen nach oben dass ich einen neuen UrlHelper erstellen und dann bestätigen kann, dass die zurückgegebene URL die erwartete war. Das ist, was ich habe, aber ich bin offen für alles, was nicht auch Spott bedeutet. ; 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")); 
    } 

Ich habe versucht, es zu urlHelper.Action („Test“, „TestAction“) zu ändern, aber es wird auf jeden Fall nicht so dass ich weiß, dass es nicht meine Extension ist, die nicht funktionieren. NUnit zurück:

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

ich überprüft haben, dass die Route registriert und arbeiten, und ich bin mit Hanselmans Erweiterung für eine gefälschte Httpcontext zu schaffen. Hier ist, was meine UrlHelper extentionmethod wie folgt aussehen:

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

Alle Ideen, was mir fehlt, um es funktioniert ??? /Kristoffer

+0

Können Sie Ihren Factory.CreateUrlHelper-Methodencode posten? – nkirkes

Antwort

11

Der Grund, warum es nicht funktioniert, ist, dass das RouteCollection-Objekt intern die ApplyAppPathModifier-Methode für HttpResponseBase aufruft.Es sieht so aus, als ob Hanselmans Pseudo-Code keine Erwartungen an diese Methode stellt, daher gibt sie null zurück, weshalb alle Aufrufe der Action-Methode auf UrlHelper eine leere Zeichenfolge zurückgeben. Die Fehlerbehebung besteht darin, eine Erwartung für die ApplyAppPathModifier-Methode des HttpResponseBase-Mocks zu erstellen, um einfach den übergebenen Wert zurückzugeben. Ich bin kein Rhino Mocks Experte, daher bin ich mir bei der Syntax nicht ganz sicher. Wenn Sie Moq verwenden, dann würde es so aussehen:

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

Oder, wenn Sie nur eine handgerollte Mock verwenden, wie etwas, das funktionieren würde:

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

Der obige Code sollte die minimal notwendige Implementierung von HttpContextBase, um einen Unit Test für den UrlHelper bestehen zu lassen. Ich habe es ausprobiert und es hat funktioniert. Hoffe das hilft.

1

Ich weiß, dass dies Ihre Frage nicht direkt beantwortet, aber gibt es einen Grund, warum Sie versuchen, Ihre eigene generische Erweiterungsmethode zu schreiben, anstatt die in der MVC-Futures-Baugruppe verfügbare zu verwenden? (Microsoft.Web.Mvc.dll) Oder versuchen Sie tatsächlich, die msft-Erweiterungsmethode zu testen?

[Bearbeiten 1] Sorry, ich dachte an die Html-Helfer-Erweiterung in Futures.

In der Zwischenzeit werde ich bei einem Unit Test versuchen zu sehen, ob ich das gleiche Ergebnis bekomme.

[Edit 2] Ok, also funktioniert das noch nicht ganz, aber es geht nicht in die Luft. Das Ergebnis ist einfach eine leere Zeichenfolge zurückzugeben. Ich nahm einige Mvc Helfer von Scott Hanselman bei this link.

spöttisch Ich habe auch eine Url.Action<TController> Methode, zusammen mit Hilfsmethoden ich von der Mvc Quelle riss:

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

Und schließlich, hier ist der Test, den ich laufen bin :

Noch nicht sicher, warum die Ausgabe eine leere Zeichenfolge ist, aber ich werde damit weitermachen, wie ich Zeit habe. Ich wäre neugierig zu wissen, ob Sie in der Zwischenzeit eine Lösung finden.

+0

Ich habe meine Beispiele aktualisiert und jetzt bekomme ich das gleiche Ergebnis wie du und ich denke, du könntest diesen Fortschritt nennen. Immer noch nicht sicher, warum ich einen leeren String zurückbekomme, da ich überprüft habe, dass meine Route funktioniert und "~/Test/TestAction" entspricht. –

+0

Ja, ich bin mir nicht sicher, warum das auch leer ist. Zuerst dachte ich, dass es daran lag, dass ich bei dem Projekt diesen Test durchgeführt habe, dass ich ein paar seltsame Routen hatte und vielleicht konnte ich keine Übereinstimmung finden. Da Sie das gleiche Ergebnis erhalten, bin ich mir nicht sicher, ob es sich um ein Routing-Problem handelt. Ich werde noch etwas spielen. – nkirkes

2

konnte ich die BuildUrlFromExpression Methode testen, aber ich brauchte, um meine RouteTable.Routes bevor die Tests registrieren ausgeführt wird:

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

Dann Stub/setup diese Eigenschaften:

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

Nach dass die BuildUrlFromExpression-Methode Uls wie erwartet zurückgegeben hat.