【问题标题】:How can you unit test an Action Filter in ASP.NET Web Api?如何在 ASP.NET Web Api 中对动作过滤器进行单元测试?
【发布时间】:2012-08-04 14:18:27
【问题描述】:

我希望在我的服务中添加一个操作过滤器,以处理将链接数据添加到响应消息中。我发现我需要模拟 HttpActionExecutedContext 但它是一个难以模拟的类,你如何处理动作过滤器测试?

【问题讨论】:

    标签: .net unit-testing asp.net-web-api action-filter


    【解决方案1】:

    引用https://stackoverflow.com/a/44447349/5547177

    您可以使用以下内容自己创建一个 HTTPActionContext:

     _ctx = new HttpActionContext
            {
                ControllerContext = new HttpControllerContext()
                {
                    Request = new HttpRequestMessage()
    
                }
            };
            _ctx.Request.Properties[System.Web.Http.Hosting.HttpPropertyKeys.HttpConfigurationKey] = new HttpConfiguration();
    

    诀窍是没有Request.Properties入口设置,它会显示错误:

    请求没有关联的配置对象或 提供的配置为空。

    这可能是设计者的疏忽,因为您可以在 HTTPActionContext 构造函数中设置 HTTPConfiguration!

    【讨论】:

      【解决方案2】:

      这是 2018 年的一个工作示例(.NET Framework 4.5.1)。它使用 ExceptionFilterAttribute,但对于其他 FilterAttributes 应该是类似的。

      [Test]
      public void MyTest()
      {
          var request = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.google.com"));
          var response = new HttpResponseMessage();
      
          // This next line is necessary to avoid the following error
          // if you call `context.Request.CreateResponse(...)` inside the filter:
          // System.InvalidOperationException: The request does not have an associated configuration object or the provided configuration was null.
          // Discovered from https://stackoverflow.com/a/44447355/3312114
          request.SetConfiguration(new HttpConfiguration());
      
          var context = ContextUtil.GetActionExecutedContext(request, response);
      
          _myFilter.OnException(context); // Execute your methods
      
          Assert.AreEqual(HttpStatusCode.InternalServerError, context.Response.StatusCode); // Make your assertions
      }
      

      然后只需将 ContextUtil 类复制到您的测试项目中的某个位置。 @thomasb 对@tugberk 答案的评论表明最新代码是on Codeplex。虽然该评论是在 2014 年,所以甚至可能会有以后的代码,但 2014 年的代码对我有用(2018 年 1 月),而原始链接代码却没有。为方便起见,我复制了下面的更高版本。只需将其放入一个新文件中即可。

      internal static class ContextUtil
      {
          public static HttpControllerContext CreateControllerContext(HttpConfiguration configuration = null, IHttpController instance = null, IHttpRouteData routeData = null, HttpRequestMessage request = null)
          {
              HttpConfiguration config = configuration ?? new HttpConfiguration();
              IHttpRouteData route = routeData ?? new HttpRouteData(new HttpRoute());
              HttpRequestMessage req = request ?? new HttpRequestMessage();
              req.SetConfiguration(config);
              req.SetRouteData(route);
      
              HttpControllerContext context = new HttpControllerContext(config, route, req);
              if (instance != null)
              {
                  context.Controller = instance;
              }
              context.ControllerDescriptor = CreateControllerDescriptor(config);
      
              return context;
          }
      
          public static HttpActionContext CreateActionContext(HttpControllerContext controllerContext = null, HttpActionDescriptor actionDescriptor = null)
          {
              HttpControllerContext context = controllerContext ?? ContextUtil.CreateControllerContext();
              HttpActionDescriptor descriptor = actionDescriptor ?? CreateActionDescriptor();
              descriptor.ControllerDescriptor = context.ControllerDescriptor;
              return new HttpActionContext(context, descriptor);
          }
      
          public static HttpActionContext GetHttpActionContext(HttpRequestMessage request)
          {
              HttpActionContext actionContext = CreateActionContext();
              actionContext.ControllerContext.Request = request;
              return actionContext;
          }
      
          public static HttpActionExecutedContext GetActionExecutedContext(HttpRequestMessage request, HttpResponseMessage response)
          {
              HttpActionContext actionContext = CreateActionContext();
              actionContext.ControllerContext.Request = request;
              HttpActionExecutedContext actionExecutedContext = new HttpActionExecutedContext(actionContext, null) { Response = response };
              return actionExecutedContext;
          }
      
          public static HttpControllerDescriptor CreateControllerDescriptor(HttpConfiguration config = null)
          {
              if (config == null)
              {
                  config = new HttpConfiguration();
              }
              return new HttpControllerDescriptor() { Configuration = config, ControllerName = "FooController" };
          }
      
          public static HttpActionDescriptor CreateActionDescriptor()
          {
              var mock = new Mock<HttpActionDescriptor>() { CallBase = true };
              mock.SetupGet(d => d.ActionName).Returns("Bar");
              return mock.Object;
          }
      }
      

      【讨论】:

      • 我基本上复制/粘贴了您的代码,但出现以下错误:System.MissingMethodException : Method not found: 'Void System.Web.Http.Controllers.HttpControllerContext.set_Request(System.Net.Http.HttpRequestMessage)'. - 有什么想法吗?
      • 代码在 .NET Framework 4.5.1 项目中工作,但是复制到 .NET Framework 4.6.2 项目中,它失败了,上面提到了MissingMethodException
      • 感谢您指出这一点。我更新了答案以专门调出 4.5.1。如果您让它与 4.6.2 一起使用,请随时根据具体情况编辑此答案
      【解决方案3】:

      我也一直在用头撞砖墙。我尝试了contextUtil,但不断收到空引用异常。我发现了如何在this post 中调用 actionFilter 注:使用过滤器的 Mock 实例时未调用 actionFilter,我必须使用真实对象。 高温

      具体来说:

      var httpActionContext = new HttpActionContext
      {
          ControllerContext = new HttpControllerContext
          {
              Request = requestMessage
          }
      };
      
      //call filter
      var filter = new FooFilter();
      filter.OnActionExecuting(httpActionContext);
      

      【讨论】:

        【解决方案4】:

        刚刚更新。

        private HttpActionContext CreateExecutingContext()
        {
            return new HttpActionContext { ControllerContext = new HttpControllerContext {   Request = new HttpRequestMessage() } };
        }
        
        private HttpActionExecutedContext CreateExecutedContextWithStatusCode(HttpStatusCode statusCode)
        {
            return new HttpActionExecutedContext
            {
                ActionContext = new HttpActionContext
                {
                    ControllerContext = new HttpControllerContext
                    {
                        Request = new HttpRequestMessage()
                    }
                },
                Response = new HttpResponseMessage
                {
                    StatusCode = statusCode,
                    Content = new StringContent("blah")
                }
            };
        }
        

        【讨论】:

        • 在许多情况下,动作过滤器会对请求对象进行一些验证,或者在 ActionFilter 上使用请求对象。在这种情况下,我只需添加您的函数以检索请求对象:私有 HttpActionContext CreateExecutingContext(YourRequestObj request) 并在函数上将数据添加到 ActionArgument,如下所示:actionContext.ActionArguments.Add("request", request);
        【解决方案5】:

        我在尝试测试我构建的自定义未处理异常过滤器时遇到了同样的问题。

        这成功了。大量的更新和很长的代码行。

        var httpActionExecutedContext = new HttpActionExecutedContext(
            new HttpActionContext(
                new HttpControllerContext(
                    new HttpConfiguration(),
                    Substitute.For<IHttpRouteData>(),
                    new HttpRequestMessage()),
            Substitute.For<HttpActionDescriptor>()),
            null);
        

        使用了 NSubstiute,但您选择的任何处理抽象基类的模拟框架都可以。

        希望对你有帮助

        【讨论】:

          【解决方案6】:

          您可以为HttpActionExecutedContext 创建一个假的,如下所示:

          public static HttpActionContext CreateActionContext(HttpControllerContext controllerContext = null, HttpActionDescriptor actionDescriptor = null)
          {
              HttpControllerContext context = controllerContext ?? ContextUtil.CreateControllerContext();
              HttpActionDescriptor descriptor = actionDescriptor ?? new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
              return new HttpActionContext(context, descriptor);
          }
          
          public static HttpActionExecutedContext GetActionExecutedContext(HttpRequestMessage request, HttpResponseMessage response)
          {
              HttpActionContext actionContext = CreateActionContext();
              actionContext.ControllerContext.Request = request;
              HttpActionExecutedContext actionExecutedContext = new HttpActionExecutedContext(actionContext, null) { Response = response };
              return actionExecutedContext;
          }
          

          我刚刚从 ASP.NET Web API 源代码中复制并粘贴了该代码:ContextUtil 类。以下是他们如何测试一些内置过滤器的一些示例:

          ActionFilterAttributeTestActionFilterAttribute 的测试类,它是一个抽象类,但你会明白的。

          【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-02-24
          • 1970-01-01
          • 2017-07-10
          相关资源
          最近更新 更多