ASP.NET Web API In-Memory Hosting Integration Test and Ninject

A while back, I posted how I used StructureMap along with the new ASP.NET self-host features to do integration tests on my ASP.NET Web API. Since that post, I've tried out Ninject with some new projects and, thanks to Filip, I discovered In Memory Web Hosting. This post will go over how to integration test an ASP.NET Web API project that uses Ninject for IoC and in memory hosting. I borrow from Filip's post with adaptations for my purposes. Specifically, I'm using Ninject for IoC and Nunit.

What's In Memory?

In-memory web hosting is similar to the self-host except it's all done -- wait for it -- in memory. Shocking! With self-host, you had to set up an HTTP endpoint on localhost and everything communicated over that. One way to tell this worked was that, in Fiddler, traffic to your self-host would show up as typical HTTP traffic.

In memory hosting differs and doesn't require that HTTP endpoint. It's simpler to set up, faster, and, to prove that it's not doing the same thing as self-host or communicating with a localhost port, no traffic shows up in Fiddler. I'm not going to show that, so you need to trust me.

My ASP.NET Web API is typical. It uses Ninject for IoC and that's all set up in the App_Start folder. I'm not going to go into much detail about that here, but for more information on that, see another post by Filip and check out the source code for this post on GitHub. In my example code, the Controller and IoC is completely contrived. It's merely there to show that Ninject is doing its job. Your project would have real work to do, but the code would look similar. Here's what the controller looks like:

  
public class SandwichController : ApiController  
{
    private readonly ISandwich _sandwich;

    public SandwichController(ISandwich sandwich)
    {
        // Totally contrived. Why would I do this? I wouldn't. I just needed something to demonstrate Ninject IoC working.
        sandwich.Description = "Deli Sandwich";
        sandwich.Id = 1;

        _sandwich = sandwich;
    }

    // GET api/sandwich/5
    public ISandwich Get(int id)
    {
        // See how lame this is? It doesn't do anything but return that one I created in the constructor.
        return _sandwich;
    }

    // POST api/sandwich
    public HttpResponseMessage Post(DeliSandwich sandwich) // <-- Note that this is an explicit class, not an interface
    {
        // Make sure we've got something. Throw a bad request if there's nothing in it.
        if (sandwich == null)
        {
            throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "sandwich cannot be null"));
        }

        var mySandwich = new DeliSandwich() { Description = sandwich.Description, Id = sandwich.Id };

        // Do something with it....I'm not going to do anything with it. You get the idea.

        return new HttpResponseMessage(HttpStatusCode.Created);

    }

}

Over to the Integration Test. Make sure to add Nunit from NuGet for the test framework. Also, I use the beta Nunit Visual Studio 2012 integration extension for a test runner to make sure I get that sweet, sweet code coverage analysis. It's not necessary for this post. Time to set up the in-memory host. You ready? Let's do this!

Set Up The Server

Here's the base class that sets up the in-memory host:

  
[TestFixture]
public abstract class BaseControllerTest : IDisposable  
{
    protected HttpServer _httpServer;
    protected const string Url = "http://notebookheavy.com/";

    [TestFixtureSetUp]
    public void SetupHttpServer()
    {
        var config = new HttpConfiguration();

        config.Routes.MapHttpRoute(name: "Default", routeTemplate: "api/{controller}/{id}/{action}", defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional });

        config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;

        // Here's where we set-up and connect to Ninject as defined in the Web API
        NinjectWebCommon.Start();
        var resolver = new NinjectResolver(NinjectWebCommon.bootstrapper.Kernel);
        config.DependencyResolver = resolver;

        _httpServer = new HttpServer(config);
    }

    public void Dispose()
    {
        if (_httpServer != null)
        {
            _httpServer.Dispose();
        }
    }

    /// 
    /// This method is taken from Filip W in a blog post located at: http://www.strathweb.com/2012/06/asp-net-web-api-integration-testing-with-in-memory-hosting/
    /// 
    /// 
    /// 
    /// 
    /// 
    protected HttpRequestMessage CreateRequest(string url, string mthv, HttpMethod method)
    {
        var request = new HttpRequestMessage
        {
            RequestUri = new Uri(Url + url)
        };

        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mthv));
        request.Method = method;

        return request;
    }

}

You'll see that it's very similar to self-host. There's a HttpConfiguration that includes the route configuration. This time, since Ninject is the IoC framework, there's a different set of steps than what I used for StructureMap, though it's essentially the same. Since I already have my IoC bindings setup in the ASP.NET Web API project, I'm just going to reference that code since it's the same code that is used in production. To make the bootstrap accessible from my test project, I made it public, rather than its original private. Sometimes you just gotta do what you gotta do.

This allows me to pull up a reference to it and access the Kernel. Once I have a reference to the Kernel, I can set it as the DependencyResolver for my in-memory host.

Test Me

Here's what the actual test looks like:


[TestFixture]
public class SandwichControllerTest : BaseControllerTest  
{
    [Test]
    [Description("This test attempts to retrieve a known sandwich based on an explicit id.")]
    public void Get_Sandwich_With_SandwichId_Success()
{
var client = new HttpClient(_httpServer);  
const int sandwichId = 1;

var request = CreateRequest(string.Format("api/sandwich/{0}", sandwichId), "application/json", HttpMethod.Get);  
using (HttpResponseMessage response = client.SendAsync(request, new CancellationTokenSource().Token).Result)  
{
Assert.NotNull(response.Content);  
Assert.AreEqual("application/json", response.Content.Headers.ContentType.MediaType);

var result = response.Content.ReadAsAsync<ISandwich>().Result;  
Assert.NotNull(result);

Assert.AreEqual(1, result.Id);  
Assert.AreEqual("Deli Sandwich", result.Description);  
Assert.That(response.IsSuccessStatusCode);  
}

request.Dispose();

}

    [Test]
    [Description("This test attempts to create a new valid sandwich.")]
    public void Create_Sandwich_Success()
    {
        var client = new HttpClient(_httpServer);

        var postAddress = string.Format("api/sandwich/");

        client.BaseAddress = new Uri(Url);

        var sandwich = new DeliSandwich() { Description = "Egg Salad Sandwich", Id = 2 }; // <---- Note that this is an explicit object, not an interface
        var postData = new StringContent(JsonConvert.SerializeObject(sandwich), Encoding.UTF8, "application/json");

        using (HttpResponseMessage response = client.PostAsync(postAddress, postData, new CancellationTokenSource().Token).Result)
        {
            Assert.That(response.IsSuccessStatusCode);
            Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Created));
        }
    }

    [Test]
    [Description("This test ensures that if nothing is sent to the controller, a BadRequest is returned.")]
    public void Create_Sandwich_Null_Data_Failure()
    {
        var client = new HttpClient(_httpServer);

        var postAddress = string.Format("api/sandwich/");

        client.BaseAddress = new Uri(Url);

        using (HttpResponseMessage response = client.PostAsync(postAddress, null, new CancellationTokenSource().Token).Result)
        {
            Assert.IsFalse(response.IsSuccessStatusCode);
            Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
        }
    }
}

From this point, I have two different methods for working with the in-memory host. One example, for a GET request, uses a simple set of headers in the HttpRequestMessage (the CreateRequest is taken from Filip).

The other, a POST, serializes a Dto object into JSON (using JSON.NET...don't forget that reference) before sending it as string content. Both do the same thing, just in different ways. Then I've got a bunch of Asserts to make sure the Web API is sending back the right stuff. One thing to note on this POST. Notice that in the SandwichController, the POST action is expecting a concrete type, not an Interface. At one point I had an ISandwich as the parameter, but the automatic binding kept coming up null.

All greens! Time to go get a sandwich. As usual, I've posted all of the source code for this in GitHub.