One of the nice things around the rising tide of OWIN in the .NET ecosystem is that it literally supports “hosting anywhere – as long as there’s a viable host!”. For fans of TDD, the great thing about this is “anywhere” includes “inside your unit tests”.
This lets you build a really powerful development workflow where you can lean entirely on your test runner while building your APIs, and not have to bother with the painful cycle of breaking out into external tools. You get to execute the entire WebAPI stack, from inside your acceptance or unit tests and get rapid feedback.
This only works if you’re using WebAPI hosted “using OWIN”, and allows you to write tests that look like this:
Combined with manipulation of your container registrations, you can execute full stack tests with a single mocked out component like your data store like this:
Powerful right? Lets take a look at how we put this together over a vanilla WebAPI controller, with a single DI’d dependency.
We’re going to use a few nuget packages – the ones provided by Microsoft to host OWIN components in IIS, some testing helpers that support in process hosting of OWIN components outside of IIS, and Ninject an IoC container. We have a packages.json that looks like this:
Our solution looks like this:
There are a couple of important pieces here – the WebAPI app with it’s OWIN Startup file, a Ninject dependency resolver to hook up IoC into WebAPI, and the controller described earlier. In addition to this we have the interface IGetValues implemented by ValueService. For this example, ValueService is very simple:
Your OWIN startup class is effectively your “Global.asax” – it bootstraps your app and configures components.
You’ll see that we’re configuring routes, creating a new Ninject “StandardKernel” IoC container, and using the Ninject.Extensions.Conventions library to bind up all our components – in this case just our IGetValues service. I’ll skip over the implementation of the Ninject dependency resolver, but all it does is delegate calls to create components to the Ninject standard kernel.
Pressing F5 in Visual Studio will launch the WebAPI app in IIS Express, and you can visit it in a browser
Now we have a working WebAPI app, lets see how we can test it in memory. We want to build a test that runs the whole of the WebAPI stack in memory, and asserts on its response.
To do this, we need to use a couple of packages – the Microsoft Owin HttpListener package, the Owin Hosting package and Owin testing package, alongside our test framework (NUnit).
With those packages installed, we can write a test that wires all these components together.
What we’re doing here is using the Owin hosting components to invoke our apps Startup class, hosted over HttpListener, on localhost on the port 8086. We’re then using a regular HTTP client to connect to this server, execute a request, and assert on a response. It’s a small marvel that we can do this at all, but TDDers will be cringing at the amount of noise in the test distracting you from the meaningful parts of your test – the preconditions and assertions. We can do better than this!
A Less Noisy Approach
Consider this class
Firstly, it’s a base class that tidies all the noise out of the way – all the boilerplate code is moved into a test fixture setup and teardown, and we’re detecting the first available free port to ensure that tests written execute on any machine they’re executed on. Using this class, our previous test becomes
instantly becoming readable and exposing only the things we really care about in the test – what we’re asking our code to do, and what the response is. We can pretty much use this base class everywhere because it’s bootstrapping our entire application for each text fixture.
Given how hard it’s previously been to test the full ASP.NET web stack, this is a revelation – you can write full stack acceptance tests without an installed web server, and with no additional infrastructure or support.
In real world examples your application likely has many components that connect to and use external resources (databases, web services) that you’ll want to isolate and replace with test doubles. With some creative use of our IoC container, we can get the best of both worlds and execute full stack acceptance tests (because at this point, we’re definitely not “unit testing”) while swapping out “just” your data access component or “just” an API client library to provide fake responses.
To do this, we want to rebind parts of our application stack for each test, and luckily, Ninject (and pretty much any other good container) makes this quite easy. Consider the following additions to our base class
It’s a little bit long winded, but what we’re doing is maintaining a dictionary of Mock objects (using Moq) and providing some helper methods for use in our unit tests. When a unit test calls the method “MockOut” a mock is generated and returned for the test to use. This Mock is “frozen” for the duration of the test fixture and rebound into our IoC container.
What this means is that we can bootstrap our entire stack, and then re-register a single component to a Mock, letting us manipulate a single external resource. This becomes exceptionally useful in our broad acceptance tests
Here you can see we’re replacing the implementation of IGetValues with a mock that returns a known value, and asserting that the full stack call returns a body with that value in it. The test is synthetic, but the application is very real – you could mock out API calls to third parties, mock out your entire data store or a single component and verify the full execution of your entire system.
These broad acceptance tests, combined with unit tests of individual components give you the perfect mix of high level “feature focused” tests (you can add a BDD framework of choice if that’s your thing) that assert on behaviour not implementation and regular “TDD Unit Tests” around the components that your system is composed from.
There are lots of parts of ASP.NET vNEXT that are “coming soon” or “wait and see”, but the community effort around OWIN and the maturation of some of the middleware frameworks makes this a technique you can and should adopt now.
Full code sample on GitHub