【问题标题】:How can I test a custom DelegatingHandler in the ASP.NET MVC 4 Web API?如何在 ASP.NET MVC 4 Web API 中测试自定义 DelegatingHandler?
【发布时间】:2012-04-05 02:32:02
【问题描述】:

我在几个地方看到过这个问题,但没有看到任何好的答案。由于我不得不自己做几次,我想我会发布我的解决方案。如果你有更好的,请留言。

注意这是使用 ASP.NET MVC 4 Beta 2 版本的 Web API - 未来版本可能会改变!

更新:这仍然适用于 ASP.NET MVC 4 RC

【问题讨论】:

    标签: c# asp.net-mvc unit-testing asp.net-mvc-4 asp.net-web-api


    【解决方案1】:

    在这种方法中,我创建了一个 TestHandler 并将其设置为被测处理程序的 InnerHandler 属性。

    然后可以将被测处理程序传递给HttpClient - 如果您正在编写服务器端处理程序,这可能看起来不直观,但这实际上是测试处理程序的一种很好的轻量级方法 - 它将被调用就像在服务器中一样。

    默认情况下,TestHandler 只会返回 HTTP 200,但它的构造函数接受一个函数,您可以使用该函数对传递的请求消息进行断言 从被测处理程序中输入。最后,您可以对来自客户端的 SendAsync 调用的结果进行断言。

    一切都设置好后,在客户端实例上调用 SendAsync 以调用您的处理程序。请求将被传递到您的处理程序中,它会将其传递给 TestHandler(假设它传递调用),然后它将响应返回给您的处理程序。

    测试处理程序如下所示:

    public class TestHandler : DelegatingHandler
    {
        private readonly Func<HttpRequestMessage,
            CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
    
        public TestHandler()
        {
            _handlerFunc = (r, c) => Return200();
        }
    
        public TestHandler(Func<HttpRequestMessage,
            CancellationToken, Task<HttpResponseMessage>> handlerFunc)
        {
            _handlerFunc = handlerFunc;
        }
    
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return _handlerFunc(request, cancellationToken);              
        }
    
        public static Task<HttpResponseMessage> Return200()
        {
            return Task.Factory.StartNew(
                () => new HttpResponseMessage(HttpStatusCode.OK));
        }
    }
    

    使用想象中的MyHandler 进行测试的示例用法。对断言使用 NUnit。:

    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
    httpRequestMessage.Headers.Add("username", "test");
    
    var handler = new MyHandler()
    {
        InnerHandler = new TestHandler((r,c) =>
        {
            Assert.That(r.Headers.Contains("username"));
            return TestHandler.Return200();
        })
    };
    
    var client = new HttpClient(handler);
    var result = client.SendAsync(httpRequestMessage).Result;
    
    Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.OK));
    

    TestHandler 的默认行为可能适用于许多测试并使代码更简单。被测处理程序的设置如下所示:

    var handler = new MyHandler();
    handler.InnerHandler = new TestHandler();
    

    我喜欢这种方法,因为它保留了测试方法中的所有断言,并且TestHandler 非常可重用。

    【讨论】:

    • 这里的请求对象是怎么处理的。 request.createresponse 失败,因为没有 httpconfiguration。因此,如果您添加一个就可以了,但是如果您对与您创建的 httprequestmessage 关联的其他请求属性感兴趣怎么办?
    • 我的目标纯粹是为了测试处理程序;那么您添加 HttpConfiguration 的建议还不够吗?你的方案是什么?
    • 我必须在 request.content、request.properties 和 request.getroutedata 周围添加包装器。测试兰迪亚一切顺利
    • 您可能想看看 AutoFixture - 可以帮助减少设置代码。
    • 我可以建议在SendAsyncawait的签名中添加async来执行_handlerFunc吗?经过TestHandler 之后,HttpContext.Current 总是最终为空
    【解决方案2】:

    我只是在寻找同样的东西,但想出了一个不使用 http 客户端的更简洁的方法。我想要一个测试来断言消息处理程序消耗了一个模拟的日志组件。我真的不需要内部处理程序来运行,只是为了“存根”它以满足单元测试。为我的目的工作:)

    //ARRANGE
            var logger = new Mock<ILogger>();
            var handler= new ServiceLoggingHandler(logger.Object);
            var request = ControllerContext.CreateHttpRequest(Guid.NewGuid(), "http://test",HttpMethod.Get);
    
            handler.InnerHandler = new Mock<HttpMessageHandler>(MockBehavior.Loose).Object;
    
            request.Content = new ObjectContent<CompanyRequest>(Company.CreateCompanyDTO(), new JsonMediaTypeFormatter());
            var invoker = new HttpMessageInvoker(handler);
    
            //ACT
            var result = invoker.SendAsync(request, new System.Threading.CancellationToken()).Result;
    
    //ASSERT
    <Your assertion>
    

    【讨论】:

      【解决方案3】:

      我为测试 DelegatingHandlers 创建了以下内容。对于使用 HttpRequestMessage.DependencyScope 使用您最喜欢的 IoC 框架解决依赖关系的处理程序很有用,例如带有 WindsorContainer 的 WindsorDependencyResolver:

          public class UnitTestHttpMessageInvoker : HttpMessageInvoker
          {
              private readonly HttpConfiguration configuration;
      
              public UnitTestHttpMessageInvoker(HttpMessageHandler handler, IDependencyResolver resolver)
              : base(handler, true)
              {
                  this.configuration = new HttpConfiguration();
                  configuration.DependencyResolver = resolver;
              }
      
              [DebuggerNonUserCode]
              public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
              {
                  if (request == null)
                  {
                      throw new ArgumentNullException("request");
                  }
      
                  request.Properties["MS_HttpConfiguration"] = this.configuration;
                  return base.SendAsync(request, cancellationToken);
              }
          }
      

      【讨论】:

        【解决方案4】:

        我也找到了这个答案,因为我有我的自定义处理程序,我想测试它 我们正在使用 NUnit 和 Moq,所以我认为我的解决方案可能对某人有所帮助

            using Moq;
            using Moq.Protected;
            using NUnit.Framework;
        namespace Unit.Tests
        {
            [TestFixture]
            public sealed class Tests1
            {
                private HttpClient _client;
                private HttpRequestMessage _httpRequest;
                private Mock<DelegatingHandler> _testHandler;
        
                private MyCustomHandler _subject;//MyCustomHandler inherits DelegatingHandler
        
                [SetUp]
                public void Setup()
                {
                    _httpRequest = new HttpRequestMessage(HttpMethod.Get, "/someurl");
                    _testHandler = new Mock<DelegatingHandler>();
        
                    _subject = new MyCustomHandler // create subject
                    {
                        InnerHandler = _testHandler.Object //initialize InnerHandler with our mock
                    };
        
                    _client = new HttpClient(_subject)
                    {
                        BaseAddress = new Uri("http://localhost")
                    };
                }
        
                [Test]
                public async Task Given_1()
                {
                    var mockedResult = new HttpResponseMessage(HttpStatusCode.Accepted);
        
                    void AssertThatRequestCorrect(HttpRequestMessage request, CancellationToken token)
                    {
                        Assert.That(request, Is.SameAs(_httpRequest));
                        //... Other asserts
                    }
        
                    // setup protected SendAsync 
                    // our MyCustomHandler will call SendAsync internally, and we want to check this call
                    _testHandler
                        .Protected()
                        .Setup<Task<HttpResponseMessage>>("SendAsync", _httpRequest, ItExpr.IsAny<CancellationToken>())
                        .Callback(
                            (Action<HttpRequestMessage, CancellationToken>)AssertThatRequestCorrect)
                        .ReturnsAsync(mockedResult);
        
                    //Act
                    var actualResponse = await _client.SendAsync(_httpRequest);
        
                    //check that internal call to SendAsync was only Once and with proper request object
                    _testHandler
                        .Protected()
                        .Verify("SendAsync", Times.Once(), _httpRequest, ItExpr.IsAny<CancellationToken>());
        
                    // if our custom handler modifies somehow our response we can check it here
                    Assert.That(actualResponse.IsSuccessStatusCode, Is.True);
                    Assert.That(actualResponse, Is.EqualTo(mockedResult));
                    //...Other asserts
                }
            }
        } 
        

        【讨论】:

          【解决方案5】:

          另一种选择可能是存根

          public class TestingHandlerStub : DelegatingHandler
          {
              private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
          
              public TestingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc)
              {
                  _handlerFunc = handlerFunc;
              }
          
              protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
              {
                  return _handlerFunc(request, cancellationToken);
              }
          }
          

          这是如何组合的:

          var handler = new YourCustomHandler()
                      {
                          InnerHandler = new TestingHandlerStub((r, c) =>
                          {
                              return Task.FromResult(httpResponseMessage);
                          })
                      };
           var client = new HttpClient(handler);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-03-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-04-14
            • 2021-12-17
            相关资源
            最近更新 更多