【问题标题】:Unittesting Url.Action (using Rhino Mocks?)单元测试 Url.Action(使用 Rhino Mocks?)
【发布时间】:2009-06-04 15:35:51
【问题描述】:

我正在尝试为这样使用的 UrlHelper 扩展方法编写测试:

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("Test", "TestAction") 但无论如何它都会失败,所以我知道这不是我的扩展方法不起作用。 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 扩展方法的样子:

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

关于我缺少什么让它工作的任何想法??? /克里斯托弗

【问题讨论】:

  • 你能发布你的 Factory.CreateUrlHelper 方法代码吗?

标签: asp.net-mvc unit-testing rhino-mocks urlhelper


【解决方案1】:

它不起作用的原因是 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 的单元测试通过。我试过了,它奏效了。希望这会有所帮助。

【讨论】:

    【解决方案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 方法按预期返回 uls。

    【讨论】:

      【解决方案3】:

      我知道这并不能直接回答您的问题,但是您是否有理由尝试编写自己的通用扩展方法而不是使用 MVC Futures 程序集中可用的方法? (Microsoft.Web.Mvc.dll) 或者您实际上是在尝试对 msft 的扩展方法进行单元测试?

      [编辑 1] 抱歉,我在考虑 Futures 中的 Html 辅助扩展。

      与此同时,我将尝试进行单元测试,看看是否得到相同的结果。

      [编辑 2] 好的,所以这还没有完全起作用,但它没有爆炸。结果只是返回一个空字符串。我从 this link.this link.Scott Hanselman 那里得到了一些 Mvc 模拟助手。

      我还创建了一个 Url.Action&lt;TController&gt; 方法,以及从 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);
          }
      

      还不确定为什么输出是一个空字符串,但我会继续搞砸这个,因为我有时间。我很想知道您是否在此期间找到了解决方案。

      【讨论】:

      • 我已经更新了我的示例,现在我得到了和你一样的结果,我想你可以称之为进步。仍然不确定为什么我得到一个空字符串,因为我已经验证我的路由正在工作并且匹配“~/Test/TestAction”。
      • 是的,我也不知道为什么会空着回来。起初我以为是因为在我运行这个测试的项目中,我有一些奇怪的路线,也许它找不到匹配项。由于您得到相同的结果,我不太确定这是路由问题。我再玩一会儿。
      猜你喜欢
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多