Mocking the request of ApiController

Topics: ASP.NET Web API
Jun 7, 2012 at 2:04 PM

I have a Web API method which throws an exception in the RC of MVC4:

public FaceBookAuthenticateResult PutAuthenticate(string facebookAccessToken)       
{
   throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, new Bar());
}

I would like to test this method, but how would I do that? When running in the test the Request property of the ApiController will be null. I can only assign a

new HttpRequestMessage()

to the request property which is not abstract and has no virtual methods so I cannot mock or stub it. If I do assign a HttpRequestMessage to the property the CreateResponse method will fail due to some missing state.

This was a lot easier with the generic HttpResponseMessage which has been removed going from beta to RC. Am I missing something... Everything else in MVC has interfaces or abstract classes (like the regular Controller class).

Jun 7, 2012 at 4:32 PM

The ApiController.Request is set-able so you should be able to just set it. A request itself is easy to create and everything is fully set-able as well.

It is correct that you need to set some state to get CreateResponse to work. Here's what should allow you to get started:

request.Properties["MS_HttpConfiguration"] = configuration;

where configuration is just a default instance of the HttpConfiguration.

We do have a work item on making easy setters for these [1] but we haven't gotten to it.

Hope this helps,

Henrik

[1] http://aspnetwebstack.codeplex.com/workitem/156

Jun 8, 2012 at 7:42 AM
Edited Jun 8, 2012 at 7:43 AM

Thank you, that does work, but I must say that I find it odd that there are two very different ways of achieving this in MVC controllers and Web API controllers. I think MVC has a far better API for this - as I can do something like this:

HttpContextBase context = Stub<HttpContextBase>();
context.Stub(context => context.Request).Return(Stub<HttpRequestBase>());

FooController foo = new FooController();
foo.ControllerContext = new ControllerContext(context, new RouteData(), foo);

In Web API it must be done like this:

BarController bar = new BarController();

HttpConfiguration configuration = new HttpConfiguration();
HttpRequestMessage request = new HttpRequestMessage();
bar.Request = request;
bar.Request.Properties["MS_HttpConfiguration"] = configuration;

So whats wrong with this:

  • Totally different API which is confusing (even with easy setters)
  • The ApiController class does have a ControllerContext property like the MVC Controller class, but setting it does not have the same semantics (actually in my case it makes no difference if it is set or not).
  • Impossible to mock the HttpRequestMessage object, so when testing it is necessary to build up the entire object by hand. It would have been nice if it was an abstract base class as in MVC.

I would like to be able to do something like this:

 

HttpRequestMessageBase request = Stub<HttpRequestMessageBase>();
//setup the request stub

BarController bar = new BarController();
bar.ControllerContext = new HttpControllerContext(Stub<HttpConfigurationBase>(), Stub<IHttpRouteData>(), request);

Or something like it.

Coordinator
Jun 8, 2012 at 5:31 PM

Why is it better to mock the HttpRequestMessage instead of just creating one and setting it's properties?

The HTTP APIs are different and while we understand that this causes some confusion the new HTTP API does have a number of benefits:

  • Generally easier to work with (ex strongly typed headers, strongly type content, etc.)
  • Not tied to any one particular host, so that we can host Web API on anything
  • Symmetric API with the new HttpClient so you have the same programming model on both sides of the wire

Because the HTTP API is used symmetrically on the client and server you can write full in memory end-to-end tests like this:

ContactManagerApplication.ContactRepository = new SampleContactRepository();
var config = new HttpConfiguration();
WebApiConfig.Configure(config);
var server = new HttpServer(config);
var client = new HttpClient(server);
var contact = new Contact() { Name = "Test" };
var response = client.PostAsJsonAsync<Contact>("http://localhost/api/contacts", contact).Result;
var postedContact = response.Content.ReadAsAsync<Contact>().Result;
Assert.IsNotNull(postedContact);

Daniel Roth 

Jun 10, 2012 at 7:50 AM
Edited Jun 10, 2012 at 7:52 AM

I guess it is a matter of ones individual coding style if you prefer mocking or creating an object using setters. Personally I would prefer mocking it as I would have a consistent style cross the request object and other mocked services + I would reduce the Web Api specific 'plumming' code I need to write in order to get the test up and running. I am sure there are others in the community who have stronger opinions on this than I have, so the point I am trying to make is that the API does not give us the choice of mocking. Which I think is a bad thing and a step down from the MVC API. I am not discussing the changes made to the Web API in genneral and I am sure you have put great efforts into creating a great API, I am just arguing that in this case I would have liked it to be more like the MVC API.

Regarding the sample test then I am sure there are many developers who would write a test like the sample you provided. There are also many who do not write end-to-end tests, but instead test the individual components of the application. I do not want to go into a Unit test discussion, but I would not want to write a test like that. What I want to do is to create an instance of the controller class (injecting dependencies in the constructor) and test one thing in each test. I genneral I find that the API very nicely supports dependency injection and think you have done a great job allowing unit tests of the controller - except for the missing abstract base classes (and I did like the generic HttpResponseMessage but I am sure you had good reasons to remove that).

So just to sum up what I am trying to say is that I cannot think of one good reason why you should not provide an abstract base class for the request object (and possible others) so that developers (like me) have the option of mocking it. I think that this is one of the great qualities of the MVC API (and something that has been neglected in far too many .net APIs) and I do hope you will include that in the final release.

Coordinator
Jun 18, 2012 at 9:45 PM

This is great feedback. Making web APIs easy to test has been one of the goals of ASP.NET Web API and there are certainly places where we can improve in this regard.

Changing HttpRequestMessage so that it can be mocked is unfortunately difficult at this point as it ships with .NET 4.5 and we need to ensure that our .NET 4 version of the API remains compatibility with the 4.5 version. Still, we can consider this change for a future release. Please open an issue in our Issue Tracker so that we can keep track of this request.

Thanks.

Daniel Roth

Mar 17 at 3:46 PM
Edited Mar 17 at 3:47 PM
danroth27 wrote:
Why is it better to mock the HttpRequestMessage instead of just creating one and setting it's properties?
It is just generally easier to deal with if you can mock things... I really just wan't to test my code, not your code... And by hitting your code I need to figure it all out, which properties do i need to set?... Ok i got so far by just setting the Request with an empty new message, but then you hit another stone and you again need to search around google to figure out a solution.

Unfortunately you do heavily use of Extension methods which generally mean that simple mocking here wouldn't suffice... Instead you may need to create a specific Mock implementation... "MockHttpRequestMessage"... Otherwise we will just have to setup our mock for all sorts of things anyways...

Generally I find it easiest to make use of delegation/wrapping. Like:
    public interface IRequestDelegator
    {
        HttpResponseMessage CreateResponse(HttpStatusCode httpStatusCode);
        HttpResponseMessage CreateResponse(HttpStatusCode httpStatusCode, BunkeringOperation entity);

        void Bind(Func<HttpRequestMessage> func);
    }

    public class RequestDelegator : IRequestDelegator
    {
        public static IRequestDelegator Default() { return new RequestDelegator(); }

        private Func<HttpRequestMessage> binder;

        private HttpRequestMessage Request
        {
            get { return binder(); }
        }

        public HttpResponseMessage CreateResponse(HttpStatusCode httpStatusCode)
        {
            return Request.CreateResponse(httpStatusCode);
        }

        public HttpResponseMessage CreateResponse(HttpStatusCode httpStatusCode, BunkeringOperation entity)
        {
            return Request.CreateResponse(httpStatusCode, entity);
        }

        //... Add methods as appropriate, or some clever "public T Delegate<T>(Func<HttpRequestMessage, T> func)" style... but that limits
        // our ability to be explicit unless you wan't to begin and compare expressions (by making it into an expression)... or something... but also
        // depends on our needs.

        public void Bind(Func<HttpRequestMessage> func)
        {
            binder = func;
        }
    }

    public class MyController : ApiController
    {
        private readonly IRequestDelegator delegator;

        public BunkeringOperationController(IRequestDelegator delegator, ... other dependencies ... )
        {
            this.delegator = delegator;
            delegator.Bind(() => Request);
        }

        [HttpGet]
        public dynamic Get([FromUri] int id)
        {
            object entity = ... get from backend ...
            if (entity != null)
            {
                return JObject.FromObject(entity);
            }
            return delegator.CreateResponse(HttpStatusCode.NotFound);
        }
Then it's fairly simple to mock the delegator and do our expectations on that... And that pattern can be continued for other object types as well...


Regardless... I Wish Microsoft would begin to use Interfaces more often though... So we could have "IHttpRequestMessage", "IHttpResponseMessage" etc...