2009-06-04 51 views
12

我試圖寫一個UrlHelper extensionmethod時使用這樣一個測試:單元測試Url.Action(使用犀牛製品?)

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

不過,我似乎無法設置正確以便我可以創建一個新的UrlHelper,然後聲明返回的url是預期的。這就是我所擁有的,但我對任何不涉及嘲笑的東西都是開放的。 ; 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")); 
    } 

我試圖將其更改爲urlHelper.Action(「測試」,「TestAction」),但讓我知道這是不是我的extensionmethod不工作就會反正失敗。 NUnit的返回:

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

我已經驗證該路由註冊和工作,我使用Hanselmans擴展創建一個假的HttpContext。這裏是我的UrlHelper extentionmethod樣子:

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

任何的想法是什麼,我缺少的得到它的工作??? /Kristoffer

+0

你可以發佈你的Factory.CreateUrlHelper方法代碼嗎? – nkirkes 2009-06-04 16:28:58

回答

11

不工作的原因是RouteCollection對象內部調用HttpResponseBase上的ApplyAppPathModifier方法。它看起來像Hanselman的模擬代碼沒有對該方法設置任何期望,所以它返回null,這就是爲什麼你對UrlHelper上的Action方法的所有調用都返回一個空字符串。解決方法是在HttpResponseBase模擬的ApplyAppPathModifier方法上設置一個期望值,以返回傳遞給它的值。我不是Rhino Mocks的專家,所以我對語法不太確定。如果您使用的起訂量,那麼就應該是這樣的:

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

或者,如果你只是用手卷模擬,像這樣的工作:

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

上面的代碼應該是HttpContextBase的最小必要實現,以便爲UrlHelper進行單元測試。我試了一下,它的工作。希望這可以幫助。

1

我知道這並不能直接回答你的問題,但是有沒有理由試圖編寫自己的通用擴展方法,而不是使用MVC Futures assembly中可用的方法? (Microsoft.Web.Mvc.dll)還是你實際上試圖單元測試msft的擴展方法?

[編輯1] 對不起,我在考慮期貨中的Html助手擴展。

與此同時,我會試着在單元測試中看看我是否得到相同的結果。

[編輯2] 好吧,所以這還沒有完全正常工作,但它並沒有炸燬。結果只是返回一個空字符串。我花了一些的mvc嘲諷斯科特Hanselman的助手在this link.

我還創建了一個Url.Action<TController>方法,連同輔助方法我從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); 
     } 
    } 
} 

最後,這裏是我運行測試:

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

不知道爲什麼輸出是一個空字符串,但我會一直這樣做,因爲我有時間。如果您在此期間找到解決方案,我會很好奇。

+0

我已經更新了我的例子,現在我得到了和你一樣的結果,我想你可以稱之爲進步。仍然不確定爲什麼我得到一個空字符串,因爲我已經驗證了我的路線正在工作並匹配「〜/ Test/TestAction」。 – 2009-06-05 07:00:15

2

我能夠測試BuildUrlFromExpression方法,但我需要在運行測試之前註冊我RouteTable.Routes:

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

然後踩滅/設置這些屬性:

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

後BuildUrlFromExpression方法按預期返回。