单元测试DelegatingHandler

本文关键字:DelegatingHandler 单元测试 | 更新日期: 2023-09-27 18:04:46

如何对自定义DelegatingHandler进行单元测试?我有以下,但它抱怨的innerHandler没有设置。

var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://foo.com");
var handler = new FooHandler()
{
    InnerHandler = new FooHandler() 
};
var invoker = new HttpMessageInvoker(handler);
var result = await invoker.SendAsync(httpRequestMessage, new CancellationToken());
Assert.That(result.Headers.GetValues("some-header").First(), Is.Not.Empty, "");

单元测试DelegatingHandler

您可以设置您正在测试的DelegatingHandler的InnerHandler属性(FooHandler)与虚拟/假处理程序(TestHandler),如您评论中的链接帖子所示。

public class TestHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
    }
}
 // in your test class method
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.com/");
    var handler = new FooHandler()
    {
        InnerHandler = new TestHandler()  // <-- change to use this
    };
    var invoker = new HttpMessageInvoker(handler);
    var result = await invoker.SendAsync(httpRequestMessage, new CancellationToken());
    Assert.That(result.Headers.GetValues("some-header").First(), Is.Not.Empty, "");

与那篇文章不同,这应该是您需要设置的最小值,以使您的测试运行

使用using Moq.Protected;,您可以更好地控制响应结果。

var request = new HttpRequestMessage();
var innerHandlerMock = new Mock<DelegatingHandler>(MockBehavior.Strict);
innerHandlerMock
  .Protected()
  .Setup<Task<HttpResponseMessage>>("SendAsync", request, ItExpr.IsAny<CancellationToken>())
  .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
var handler = new FooHandler()
{
  InnerHandler = innerHandlerMock.Object
};
var invoker = new HttpMessageInvoker(handler);
// act
await invoker.SendAsync(request, default);

使用System.Net.Http.HttpClientHandler或System.Web.Http.HttpServer作为InnerHandler:

var handler = new FooHandler()
{
    InnerHandler = new System.Net.Http.HttpClientHandler()
};

var handler = new FooHandler()
{
    InnerHandler = new System.Web.Http.HttpServer() 
};

您只需要将FooHandlerInnerHandler连接到类型为HttpMessageHandler的实例。

您可以模拟使用AutoFixture作为fixture.Create<HttpMessageHandler>(),或者您可以处理您自己的滚动,或者您可以使用令人敬畏的MockHttpMessageHandler

TLDR

这里的答案有一些小的东西,所以我想加上我的答案。我记得在Ray的回答中看到过类似的代码,我被Task.Factory.StartNew()的使用所迷惑,这是完全没有必要的。Moq.Protected对我的口味来说有点脆;在这种情况下,"SendAsync"方法不会改变。

委托处理程序遵循责任链模式,其中可以形成链。不能控制最后一个处理程序,它将是HttpMessageHandler,一个抽象类。

所以,链的最简单形式是

HttpClient -> HttpMessageHandler impl

在你的例子中,链是,

HttpMessageInvoker -> FooHandler -> HttpMessageHandler impl

用管道类HttpMessageInvoker代替陶瓷类HttpClient进行测试比较方便。在您的示例中,HttpMessageInvoker ctor处理将InnerHandlerFooHandler连接起来的部分,但您需要将FooHandlerInnerHandler连接起来。注意,对于HttpClient不需要这样做,因为存在一个助手方法HttpClientFactory.Create()来处理连接。但是没有HttpMessageInvokerFactory.Create()方法。在我的repo中,我有几个测试,在这些测试中,链中有几个处理程序,我最终编写了那个helper方法(粘贴在下面)。

注意,只有HttpMessageInvokerFactory处理布线部分;对于其他人,你必须明确地去做。

把这一切放在一起,让我们说一个简单的委派处理程序测试作为RelayHandler,它只是中继http请求,

    /// <summary>
    /// Simply relays request, just for fun.
    /// </summary>
    public class RelayHandler : DelegatingHandler
    {
        /// <inheritdoc cref="RelayHandler" />
        public RelayHandler()
        { }
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            return base.SendAsync(request, cancellationToken);
        }
    }

来源:https://github.com/rmandvikar/delegating-handlers/blob/main/src/rm.DelegatingHandlers/RelayHandler.cs

可以这样测试

        [Test]
        public async Task Relays()
        {
            var fixture = new Fixture().Customize(new AutoMoqCustomization());
            var relayHandler = new RelayHandler();
            using var invoker = HttpMessageInvokerFactory.Create(
                fixture.Create<HttpMessageHandler>(), relayHandler);
            using var requestMessage = fixture.Create<HttpRequestMessage>();
            using var response = await invoker.SendAsync(requestMessage, CancellationToken.None);
            Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
        }

来源:https://github.com/rmandvikar/delegating-handlers/blob/main/tests/rm.DelegatingHandlersTest/RelayHandlerTests.cs

其中HttpMessageInvokerFactory为,

    public static class HttpMessageInvokerFactory
    {
        public static HttpMessageInvoker Create(
            params DelegatingHandler[] handlers)
        {
            return Create(null!, handlers);
        }
        public static HttpMessageInvoker Create(
            HttpMessageHandler innerHandler,
            params DelegatingHandler[] handlers)
        {
            if (handlers == null || !handlers.Any())
            {
                throw new ArgumentNullException(nameof(handlers));
            }
            if (handlers.Any(x => x == null))
            {
                throw new ArgumentNullException(nameof(handlers), "At least one of the handlers is null.");
            }
            var first = handlers[0];
            Array.Reverse(handlers);
            var current = innerHandler;
            foreach (var next in handlers)
            {
                if (current != null)
                {
                    next.InnerHandler = current;
                }
                current = next;
            }
            return new HttpMessageInvoker(first);
        }
    }

来源:https://github.com/rmandvikar/delegating-handlers/blob/main/tests/rm.DelegatingHandlersTest/misc/HttpMessageInvokerFactory.cs

需要设置FooHandlerInnerHandler的原因是因为它的InnerHandler被执行了是我在你的测试中猜测的。也就是说,可以有一个"短路"的委托处理程序。调用,一个不需要它的InnerHandler .

这是一个无条件抛出的委托处理程序ThrowingHandler

    /// <summary>
    /// Throws an exception.
    /// </summary>
    public class ThrowingHandler : DelegatingHandler
    {
        private readonly Exception exception;
        /// <inheritdoc cref="ThrowingHandler" />
        public ThrowingHandler(
            Exception exception)
        {
            // funny, no?
            this.exception = exception
                ?? throw new ArgumentNullException(nameof(exception));
        }
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            throw exception;
        }
    }

来源:https://github.com/rmandvikar/delegating-handlers/blob/main/src/rm.DelegatingHandlers/ThrowingHandler.cs

我不需要在测试中设置它的InnerHandler,因为它没有使用。耸耸肩:如果他们想有一个一致的测试设置,可以。

        [Test]
        public void Throws()
        {
            var fixture = new Fixture().Customize(new AutoMoqCustomization());
            var throwingHandler = new ThrowingHandler(new TurnDownForWhatException());
            using var invoker = HttpMessageInvokerFactory.Create(
                throwingHandler);
            using var requestMessage = fixture.Create<HttpRequestMessage>();
            var ex = Assert.ThrowsAsync<TurnDownForWhatException>(async () =>
            {
                using var _ = await invoker.SendAsync(requestMessage, CancellationToken.None);
            });
        }
// TurnDownForWhatException is a custom exception

来源:https://github.com/rmandvikar/delegating-handlers/blob/main/tests/rm.DelegatingHandlersTest/ThrowingHandlerTests.cs

请注意,Ray回答中的TestHandler最好被命名为HttpStatusOkHandler。但是,可能需要一个处理程序来欺骗Http 400,等等。

    public class HttpStatusOkHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
        }
    }

所以,最后我想出了下面这个。

    /// <summary>
    /// Short-circuits with a canned http response.
    /// </summary>
    /// <remarks>
    /// Canned response should not be disposed if used with a retry handler
    /// as it's meant for multiuse. If so, consider using <see cref="ShortCircuitingResponseHandler"/>
    /// instead.
    /// </remarks>
    public class ShortCircuitingCannedResponseHandler : DelegatingHandler
    {
        private readonly HttpResponseMessage response;
        /// <inheritdoc cref="ShortCircuitingCannedResponseHandler" />
        public ShortCircuitingCannedResponseHandler(
            HttpResponseMessage response)
        {
            this.response = response
                ?? throw new ArgumentNullException(nameof(response));
        }
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            return Task.FromResult(response);
        }
    }

来源:https://github.com/rmandvikar/delegating-handlers/blob/main/src/rm.DelegatingHandlers/ShortCircuitingCannedResponseHandler.cs

相关文章:
  • 没有找到相关文章