如何在ASP.NET MVC 4 Web API中测试自定义DelegatingHandler?

时间:2012-03-20 15:38:52

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

我已经看到这个问题出现在一些地方,并没有看到任何好的答案。由于我自己不得不这样做几次,我想我会发布我的解决方案。如果您有更好的信息,请发布。

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

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

5 个答案:

答案 0 :(得分:47)

在这种方法中,我创建了一个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非常可重用。

答案 1 :(得分:5)

我只是在寻找相同的东西,但想出了一个更简洁的方法,没有使用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>

答案 2 :(得分:1)

我为测试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);
        }
    }

答案 3 :(得分:0)

我也找到了这个答案,因为我有自定义处理程序,我想测试它  我们正在使用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
        }
    }
} 

答案 4 :(得分:0)

另一种选择可能是存根

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