
Integration Test ASP.NET Web API with StructureMap
Update 2: I have written a new post going over how to setup StructureMap with the RC of ASP.NET Web API. Go check that out as well.
Update: This post was written against the Beta of WebAPI. Word out is that this will change for the release candidate. I have not yet done a new post for the release candidate. You can read some about these changes here. I intend to write a new post once the RC has been released.
One of the best new features of ASP.NET Web API is the ability to self host. This means that inside of our integration test, we can create a web server in memory, work with the web server through a web client (in memory as well), and not have IIS running on the box or on some other server to test. It’s all done “in-house”.
I’m using Nunit for testing and StructureMap for Inversion of Control (IOC). I’ve got two projects. The first is a MVC 4 Application with a Web API ApiController. The second project is a standard test project with Nunit. I’m on Visual Studio 11 on Windows 7. Let’s dig in. First, the controller I’m trying to integration test:
[sourcecode language=“csharp”] public class DrinkLogsController : ApiController { private readonly IDrinkTrackerContext _context;
public DrinkLogsController(IDrinkTrackerContext context)
{
_context = context;
}
[ValidationActionFilter]
public HttpResponseMessage PostDrinkLog(DrinkLog value)
{
.
.
.
// Code for the post handler goes here. It's not relevant to the discussion at hand.
}
} [/sourcecode]
So what do we have? A typical ApiController for a entity called DrinkLogs. This controller is taking in an IDrinkTrackerContext which in reality is actually an instance of a DbContext. But since we’re using IoC and interfaces, the controller doesn’t really know much about the implementation of what we’ve passed in. No tight coupling here! There’s a POST handler that’s taking in a new DrinkLog and doing something with. We don’t care what it’s doing really. At least not for the purposes of this blog entry. Now let’s look at the test harness.
[sourcecode language=“csharp”] using System; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Web.Http; using System.Web.Http.SelfHost; using DrinkTracker.Core.Model; using DrinkTracker.WebAPI; using DrinkTracker.WebAPI.Controllers; using DrinkTracker.WebAPI.Filters; using NUnit.Framework; using Newtonsoft.Json;
namespace DrinkTracker.Test
{ [TestFixture] public class DrinkLogApiIntegrationTest {
[Test]
public void Post_Drink_Log_With_No_Drink_Failure()
{
var baseAddress = "http://localhost:8080/";
var selfHostConfig = new HttpSelfHostConfiguration(baseAddress);
selfHostConfig.Filters.Add(new ValidationActionFilter());
selfHostConfig.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
selfHostConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
var container = IoC.Initialize();
var resolver = new SmDependencyResolver(container);
selfHostConfig.ServiceResolver.SetResolver(resolver.GetService, resolver.GetServices);
var server = new HttpSelfHostServer(selfHostConfig);
var client = new HttpClient();
server.OpenAsync().Wait();
client.BaseAddress = new Uri(baseAddress);
var newLog = new DrinkLog { PersonId = 1, LogDate = DateTime.UtcNow };
var postData = new StringContent(JsonConvert.SerializeObject(newLog), Encoding.UTF8, "application/json");
var r = client.PostAsync("api/drinklogs", postData);
Assert.That(r.Result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
}
} [/sourcecode]
Before I get too far explaining this test, it’s imperative to note that in order to use self hosting, you need to setup an http url namespace resolution. This is described in detail over on the Asp.Net website. Go read that, do what it says, and come back.
Too lazy to go check it out? Here’s what it says: you need to open up a developer command prompt and execute the following command (with elevated privledges):
netsh http add urlacl url=http://+:8080/ user=machineusernameNow let’s look at the test code. Right off the bat we’re setting up a location for the server to listen at. Then, using that address, we can create a HttpSelfHostConfiguration. This is where we’ll put all the secret sauce. You’ll need to do all your MVC setup work that would normally be done in the Global.asax or in the App_Start folders (where StructureMap for MVC is setup) of your MVC project. Easy enough to just go look at your MVC project and see what you’re setting up. In my case, I’m registering an ActionFilter and a Route. Set this here because the Global.asax is part of the ASP.NET runtime which we’re not really going through. I also set the IncludeErrorDetailPolicy to always to make sure I get back detailed information about any problems.
Now we move on to the IoC. Like I said, I’m using StructureMap because that’s what I’ve been using for MVC for a couple years. You could use your own flavor of IoC such as Ninject or Unity, but what’s important in this code snippet is that you get a reference to an IDependencyResolver.
Lets assume that I’ve got StructureMap setup as I want it inside the MVC project. In this case, calling IoC.Initialize() will setup StructureMap just as its setup in the MVC project. The SmDependencyResolver gives an IDependencyResolver we so longingly need which we then use to set the ServiceResolver on the HttpSelfHostConfiguration.
The hard part is done now. It’s all smooth sailing from here on out. We setup the HttpSelfHostServer passing it the configuration we so lovingly crafted earlier. Use server.OpenAsync().Wait() to put the server into a zen state of acceptance of all connections on the address we specified earlier.
Then it’s just standard HttpClient stuff. In this case, passing in a JSON representation of the new DrinkLog. Since this test is specifically testing to make sure that if the caller forgets a piece of data on a POST, we return a BadRequest HttpStatusCode. Once that’s tested, we’re golden. Or, more appropriately, all green.
If your Result.StatusCode is 500, Internal Server Error, check the error detail. If you see an error like “No parameterless constructor defined for this object.”, then your IoC isn’t setup correctly. Set a breakpoint in your constructor to confirm to yourself that you’re not setup correctly. This is ususally caused by not telling the IoC system about your bindings. Fix that, try again. If it still doesn’t work, ask a question.
Happy Web APIing.