C# 如何在ASP.NET MVC 4 Web API中测试自定义DelegatingHandler?
我在一些地方看到过这个问题,但没有看到任何好的答案。因为我自己已经做过几次了,所以我想我应该发布我的解决方案。如果你有更好的,请张贴 注意:这是使用ASP.NET MVC 4 Beta 2版本的Web API-未来版本可能会更改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 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);