簡體   English   中英

如何在 ASP.NET MVC 4 Web API 中測試自定義 DelegatingHandler?

[英]How can I test a custom DelegatingHandler in the ASP.NET MVC 4 Web API?

我在幾個地方看到過這個問題,但沒有看到任何好的答案。 由於我不得不自己做幾次,我想我會發布我的解決方案。 如果你有更好的,請留言。

注意這是使用 ASP.NET MVC 4 Beta 2 版本的 Web API - 未來版本可能會改變!

更新:這仍然適用於 ASP.NET MVC 4 RC

在這種方法中,我創建了一個 TestHandler 並將其設置為InnerHandler處理程序的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非常可重用。

我只是在尋找同樣的東西,但想出了一種更簡潔的方法,不使用 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>

我創建了以下用於測試 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);
        }
    }

我也找到了這個答案,因為我有我的自定義處理程序,我想測試它我們正在使用 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
        }
    }
} 

另一種選擇可能是存根

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM