Как я могу протестировать пользовательский DelegatingHandler в веб-API ASP.NET MVC 4? - PullRequest
31 голосов
/ 20 марта 2012

Я видел, как этот вопрос возникал в нескольких местах, и не видел хороших ответов.Поскольку мне приходилось делать это несколько раз, я решил опубликовать свое решение.Если у вас есть что-то лучше, пожалуйста, напишите.

NB Это использует ASP.NET MVC 4 Beta 2 версия Web API - будущие версии могут измениться!

Обновление: Это все еще работает в ASP.NET MVC 4 RC

Ответы [ 4 ]

48 голосов
/ 20 марта 2012

В этом подходе я создаю 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 очень многократно используется.

5 голосов
/ 03 июля 2015

Я просто искал то же самое, но придумал более лаконичный подход, в котором не использовался 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>
1 голос
/ 24 сентября 2014

Я создал следующее для тестирования DelegatingHandlers.Это полезно для обработчиков, которые используют HttpRequestMessage.DependencyScope для разрешения зависимостей, используя вашу любимую платформу IoC, например, WindsorDependencyResolver с WindsorContainer:

    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);
        }
    }
0 голосов
/ 01 февраля 2018

Я также нашел этот ответ, потому что у меня есть свой собственный обработчик, и я хочу его протестировать. Мы используем 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
        }
    }
} 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...