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

C# 如何在ASP.NET MVC 4 Web API中测试自定义DelegatingHandler?,c#,asp.net-mvc,unit-testing,asp.net-mvc-4,asp.net-web-api,C#,Asp.net Mvc,Unit Testing,Asp.net Mvc 4,Asp.net Web Api,我在一些地方看到过这个问题,但没有看到任何好的答案。因为我自己已经做过几次了,所以我想我应该发布我的解决方案。如果你有更好的,请张贴 注意:这是使用ASP.NET MVC 4 Beta 2版本的Web API-未来版本可能会更改 更新:这在ASP.NET MVC 4 RC中仍然有效。在这种方法中,我创建了一个TestHandler,并将其设置为被测试处理程序的InnerHandler属性 然后可以将被测试的处理程序传递给HttpClient——如果您正在编写服务器端处理程序,这可能看起来不直观,

我在一些地方看到过这个问题,但没有看到任何好的答案。因为我自己已经做过几次了,所以我想我应该发布我的解决方案。如果你有更好的,请张贴

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


更新:这在ASP.NET MVC 4 RC中仍然有效。在这种方法中,我创建了一个TestHandler,并将其设置为被测试处理程序的
InnerHandler
属性

然后可以将被测试的处理程序传递给
HttpClient
——如果您正在编写服务器端处理程序,这可能看起来不直观,但这实际上是测试处理程序的一种非常轻量级的方法——它将以与服务器中相同的方式被调用

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

设置好所有内容后,在客户端实例上调用
SendAsync
,以调用处理程序。请求将被传递到您的处理程序中,它将把请求传递给TestHandler(假设它传递了调用),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));
    }
}
var handler = new MyHandler();
handler.InnerHandler = new TestHandler();
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));
    }
}
var handler = new MyHandler();
handler.InnerHandler = new TestHandler();

我喜欢这种方法,因为它保留了测试方法中的所有断言,
TestHandler
非常可重用。它对于使用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);
        }
    }
公共类UnitTestHttpMessageInvoker:HttpMessageInvoker
{
私有只读HttpConfiguration;
公共UnitTestHttpMessageInvoker(HttpMessageHandler处理程序,IDependencyResolver解析程序)
:base(处理程序,true)
{
this.configuration=新的HttpConfiguration();
configuration.DependencyResolver=解析程序;
}
[调试器非用户代码]
公共覆盖任务SendAsync(HttpRequestMessage请求,CancellationToken CancellationToken)
{
if(请求==null)
{
抛出新的ArgumentNullException(“请求”);
}
request.Properties[“MS_HttpConfiguration”]=this.configuration;
返回base.sendaync(请求、取消令牌);
}
}

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

//排列
var logger=newmock();
var handler=newServiceLoggingHandler(logger.Object);
var request=ControllerContext.CreateHttpRequest(Guid.NewGuid(),”http://test“,HttpMethod.Get);
handler.InnerHandler=newmock(MockBehavior.Loose).Object;
request.Content=newobjectcontent(Company.CreateCompanyDTO(),new JsonMediaTypeFormatter());
var invoker=newhttpmessageinvoker(处理程序);
//表演
var result=invoker.sendaync(请求,new System.Threading.CancellationToken()).result;
//断言

我也找到了这个答案,因为我有我的自定义处理程序,我想测试它 我们正在使用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
        }
    }
} 
使用Moq;
使用最小起重量保护;
使用NUnit.Framework;
名称空间单元测试
{
[测试夹具]
公开密封类测试1
{
私有HttpClient \u客户端;
私有HttpRequestMessage_httpRequest;
私有模拟测试处理器;
私有MyCustomHandler _subject;//MyCustomHandler继承DelegatingHandler
[设置]
公共作废设置()
{
_httpRequest=newhttprequestmessage(HttpMethod.Get,“/someurl”);
_testHandler=newmock();
_主题=新建MyCustomHandler//创建主题
{
InnerHandler=\u testHandler.Object//使用模拟初始化InnerHandler
};
_client=新的HttpClient(\u主题)
{
BaseAddress=新Uri(“http://localhost")
};
}
[测试]
给定的公共异步任务_1()
{
var mockedResult=新的HttpResponseMessage(HttpStatusCode.Accepted);
void AssertThatRequestCorrect(HttpRequestMessage请求,CancellationToken令牌)
{
Assert.That(请求,Is.SameAs(_-httpRequest));
//……其他主张
}
//设置受保护的SendAsync
//我们的MyCustomHandler将在内部调用SendAsync,我们希望检查此调用
_测试处理器
.Protected()
.Setup(“sendsync”,_httpRequest,ItExpr.IsAny())
.回拨(
(操作)资产请求正确)
.ReturnsAsync(mockedResult);
//表演
var actualResponse=await _client.sendsync(_httpRequest);
//检查对SendAsync的内部调用是否只有一次,并且请求对象是否正确
_测试处理器
.Protected()
.Verify(“sendsync”,Times.Once(),_httpRequest,ItExpr.IsAny());
//如果我们的自定义处理程序以某种方式修改了我们的响应,我们可以在这里检查它
Assert.That(actualResponse.IsSuccessStatusCode,Is.True);
Assert.That(实际响应,Is.EqualTo(
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);