C# – Unit testing WebApi controllers in WebApi

asp.net-web-apicunit testing

I am trying to unit test my controller, but as soon as this controller uses its embedded UrlHelper object, it throws an ArgumentNullException.

The action I'm trying to test is this one:

    public HttpResponseMessage PostCommandes(Commandes commandes)
    {
        if (this.ModelState.IsValid)
        {
            this.db.AddCommande(commandes);

            HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.Created, commandes);

            // this returns null from the test project
            string link = this.Url.Link(
                "DefaultApi",
                new
                {
                    id = commandes.Commande_id
                });
            var uri = new Uri(link);
            response.Headers.Location = uri;

            return response;
        }
        else
        {
            return this.Request.CreateResponse(HttpStatusCode.BadRequest);
        }
    }

My test method looks like this:

    [Fact]
    public void Controller_insert_stores_new_item()
    {
        // arrange
        bool isInserted = false;
        Commandes item = new Commandes() { Commande_id = 123 };
        this.fakeContainer.AddCommande = (c) =>
            {
                isInserted = true;
            };
        TestsBoostrappers.SetupControllerForTests(this.controller, ControllerName, HttpMethod.Post);

        // act
        HttpResponseMessage result = this.controller.PostCommandes(item);

        // assert
        result.IsSuccessStatusCode.Should().BeTrue("because the storage method should return a successful HTTP code");
        isInserted.Should().BeTrue("because the controller should have called the underlying storage engine");

        // cleanup
        this.fakeContainer.AddCommande = null;
    }

And the SetupControllerForTests method is this one, as seen here:

    public static void SetupControllerForTests(ApiController controller, string controllerName, HttpMethod method)
    {
        var request = new HttpRequestMessage(method, string.Format("http://localhost/api/v1/{0}", controllerName));
        var config = new HttpConfiguration();
        var route = WebApiConfig.Register(config).First();
        var routeData = new HttpRouteData(route, new HttpRouteValueDictionary
                                                 {
                                                     {
                                                             "controller",
                                                             controllerName
                                                     }
                                                 });

        controller.ControllerContext = new HttpControllerContext(config, routeData, request);
        controller.Request = request;
        controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
        controller.Request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
    }

This is a pretty well documented problem for WebApi2, you can read more about it here for instance ("testing link generation"). Basically, it boils down to either setting a custom ApiController.RequestContext, or mocking the controller's Url property.

The problem is that, in my version of WebApi (Nuget packages: Microsoft.AspNet.WebApi 4.0.20710.0 / WebApi.Core.4.0.30506.0), ApiController.RequestContext does not exist, and Moq cannot mock the UrlHelper class, because the method it should mock (Link) is not overridable, or something like that (I didn't dwell on it). Because I'm using WebApi 1. But the blog post I based my code on (as well as many other posts) use V1, too. So I don't understand why it doesn't work, and most of all, how I can make it work.

Thank you !

Best Answer

Not sure if the documentation you linked to was updated since your original post but they show an example where they mock up the UrlHelper and also the Link method.

[TestMethod]
public void PostSetsLocationHeader_MockVersion()
{
    // This version uses a mock UrlHelper.

    // Arrange
    ProductsController controller = new ProductsController(repository);
    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();

    string locationUrl = "http://location/";

    // Create the mock and set up the Link method, which is used to create the Location header.
    // The mock version returns a fixed string.
    var mockUrlHelper = new Mock<UrlHelper>();
    mockUrlHelper.Setup(x => x.Link(It.IsAny<string>(), It.IsAny<object>())).Returns(locationUrl);
    controller.Url = mockUrlHelper.Object;

    // Act
    Product product = new Product() { Id = 42 };
    var response = controller.Post(product);

    // Assert
    Assert.AreEqual(locationUrl, response.Headers.Location.AbsoluteUri);
}