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

12,210

Solution 1

In this approach, I create a TestHandler and set it as the InnerHandler property of the handler under test.

The handler under test can then be passed to an HttpClient - this may seem unintuitive if you are writing a server-side handler, but this is actually a great light-weight way to test a handler - it will be called in the same way it would in a server.

The TestHandler will just return an HTTP 200 by default, but it's constructor accepts a function you can use to make asserts about the request message passed in from the handler under test. Finally you can make asserts on the result of the SendAsync call from the client.

Once everything is set up, call SendAsync on the client instance to invoke your handler. The request will be passed into your handler, it will pass this on to the TestHandler (assuming it passes the call on) which will then return a response back to your handler.

The test handler looks like this:

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

Example usage with an imagined MyHandler under test. Uses NUnit for the asserts.:

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

The default behaviour of TestHandler is probably fine for many tests and makes the code simpler. The setup of the handler under test then looks like this:

var handler = new MyHandler();
handler.InnerHandler = new TestHandler();

I like this approach because it keeps all the assertions in the test method, and the TestHandler is very reusable.

Solution 2

I was just looking for the same thing but came up with a more concise approach that didn't use http client. I wanted a test to assert the message handler consumed a mocked logging component. I didn't really need the inner handler to function, just to "stub" it out to satisfy the unit test. Works for my purpose :)

//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>

Solution 3

I created the following for testing DelegatingHandlers. It is useful for handlers that use the HttpRequestMessage.DependencyScope to resolve dependencies using your favorite IoC framework e.g. a WindsorDependencyResolver with a 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);
        }
    }

Solution 4

I also found this answer because i have my custom handler and i want to test it We are using NUnit and Moq, so i think my solution can be helpful for someone

    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
        }
    }
} 
Share:
12,210
James World
Author by

James World

I am an independent consultant working on a contract-only basis, I am currently fully engaged. I am a polyglot programmer, although most of my professional programming experience is in C#, which I both learned and taught over nearly a decade at Microsoft, where I also ran the performance and scalability labs for 3 years. I am full-stack developer, equally comfortable working on front and back-end systems. I have a passion for software development. This is backed by a breadth and depth of professional experience spanning over 20 years. I have worked predominantly for blue chip companies in the financial sector on Microsoft platforms. I am a regular speaker at .NET user groups and contributor on Stack Overflow where I am the #1 contributor on the Reactive Extensions tag (system.reactive). I am a passionate advocate of agile patterns and practices. I enjoy presenting to and mentoring others on what I have learned (and am still learning) about software theory, technology and disciplines.

Updated on June 03, 2022

Comments

  • James World
    James World almost 2 years

    I've seen this question come up in a few places, and not seen any great answers. As I've had to do this myself a few times, I thought I'd post my solution. If you have anything better, please post.

    N.B. This is using ASP.NET MVC 4 Beta 2 version of Web API - future versions may change!

    Update: This still works in ASP.NET MVC 4 RC

  • Steve
    Steve over 11 years
    how do you deal with the request object in here. request.createresponse fails since there is no httpconfiguration. so if you add one you can get by, but what if you are interested in other request properties associated with the httprequestmessage you've created?
  • James World
    James World over 11 years
    My goal here is purely to test the handler; so isn't your suggestion of adding the HttpConfiguration sufficient? What's your scenario?
  • Steve
    Steve over 11 years
    I had to add wrappers around request.content, request.properties, and request.getroutedata. all is well in test landia
  • James World
    James World over 11 years
    You might want to take a look at AutoFixture - could help cut down on setup code.
  • Gabriel
    Gabriel about 9 years
    Can I suggest to add async to the signature of SendAsync and await the execution of _handlerFunc? HttpContext.Current always ended up being null after going through the TestHandler
  • angularsen
    angularsen over 6 years
    You should generally not use Task.Factory.StartNew(), it may be fine for tests but generally it is an easy way to shoot oneself in the foot due to the not so obvious nature of schedulers and synchronization contexts. Instead for this case I propose using Task.FromResult().