
A couple of days ago a colleague pinged me wanting to talk about unit testing an ASP.NET Web API project. In particular he was having a hard time testing the POST controller, but it got me thinking I needed to explore unit testing the new Web API stuff.
Since it is always fun to add unit tests to someone else’s codebase, I decided to start by using the tutorial called Creating a Web API that Supports CRUD Operations and the provided solution available on www.asp.net.
What should we test?
In a Web API project, one of the things you need to ask yourself is, “What do we need to test?”
Despite my passion for unit testing and TDD, you might be surprised when I answer “as little as possible.” You see, when I’m adding tests to legacy code, I believe strongly that you should only add tests to the things that need it. There is very little value-add in spending hours adding tests to things that might not need it.
I tend to follow the WELC approach, focusing on adding tests to either areas of code that I am about to work on, or areas that I know need some test coverage. The goal when adding tests for legacy code like this is to “pin” the behavior down, so you at least can make positive statements about what it does do right now. But I only really care about “pinning” those methods that have interesting code in them or code we are likely to want to change in the future. (Many thanks to my friend Arlo Belshee for promoting the phrase “pinning test” for this concept. I really like it.)
So I’m not going to bother putting any unit tests on things like
BundleConfig, FilterConfig, or RouteConfig. These classes really just
provide an in-code way of configuring the various conventions in ASP.NET MVC
and Web API. I’m also not going to bother with any of the code in the
Content or Views folders, nor will I unit test any of the JavaScript (but
if this were not just a Web API, but a full web app with important JavaScript,
I would certainly think more about that last one).
Since this is a Web API project, its main purpose is to provide an easy to use
REST JSON API that can be used from apps or web pages. All of the code that
matters is in the Controllers folder, and in particular the
ProductsController class, which is the main API for the project.
This is the class we will unit test today.
Unit Tests, not Integration Tests
Notice that I said unit test in the previous sentence. For me a unit test is the smallest bit of code that I can test in isolation from other bits of code. In .NET code, this tends to be classes and methods. Defining unit test in this way makes it easy to find what to test, but sometimes the how part can be tough because of the “in isolation from other bits” part.
When we create tests that bring up large parts of our system, or of the environment, we are really creating integration tests. Don’t get me wrong, I think integration tests are useful and can be important, but I do not want to get into the habit of depending entirely on integration tests when writing code. Creating a testable, cohesive, decoupled design is important to me. It is the only way to achieve the design goal of simplicity (maximizing the amount of work not done).
But in this case we will be adding tests to an existing system. To make the point, I will try to avoid changing the system if I can. Because of this we may find ourselves occasionally creating integration tests because we have no choice. But we can (and should) use that feedback to think about the design of what we have and whether it needs some refactoring.
Analyzing the ProductsController class
The ProductsController class isn’t too complex, so it should be pretty easy
to test. Let’s take a look at the code we got in the download:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
We have three Get methods, and a method for each of Post, Put and Delete.
Straight away I see the first problem: The IProductRepository is private and
static. Since I said I didn’t want to change the product code, this is an issue.
As a static, readonly, private field, we really don’t have any way to replace
it, so in this one case, I will need to change the product to a more testable
design. This isn’t as bad as it looks, however, since in the tutorial they
acknowledge that this is a temporary measure in their code:
Calling new ProductRepository() in the controller is not the best design, because it ties the controller to a particular implementation of IProductRepository. For a better approach, see Using the Web API Dependency Resolver.
In a future post I will show how to resolve this dependency with something like
Ninject, but for now we will just use manual dependency injection by creating a
testing constructor. First I will make the repository field non-static. Then I
add a second constructor which allows me to pass in a repository. Finally I
update the default constructor to initialize the field with an instance of the
concrete ProductRepository class.
This approach of creating a testing constructor is a good first step, even if you are going to later add a dependency injection framework. It allows us to provide a stub value for the dependency when we need it, but existing clients of the class can continue to use the default constructor.
Now the class looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Testing the easy stuff
Now that we can use the testing constructor to provide a custom instance
of the IProductRepository, we can get back to writing our unit tests.
For these tests I will be using the xUnit.net unit testing framework. I will
also be using Visual Studio 2012 Fakes to provide easy-to-use Stubs for
interfaces we depend on like IProductRepository. After using NuGet to get an
xUnit.net reference in the test project, I added a project reference to the
ProductsStore project. Then by right-clicking on the ProductsStore
reference and choosing Add Fakes Assembly, I can create the Stubs I will
use in my tests.
Testing all of the methods except for PostProduct is pretty straightforward.
GetAllProducts
This is a very simple method that just returns whatever the repository
gives it. No transformations, no deep copies, it just returns the same
IEnumerable it gets from the repository. Here’s the test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
GetProduct
I used two tests to pin the existing behavior of the GetProduct method. The first
confirms that it returns what the repository gives it, and the second confirms
that it will throw if the repository returns null.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
GetProductsByCategory
I just used one test to pin the behavior of GetProductsByCategory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
PutProduct
I used three tests to pin the various aspects of the PutProduct method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
DeleteProduct
Like the PUT handler, we had a few cases to handle to correctly pin the behavior of this method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Testing the harder stuff: PostProduct
The PostProduct method is where things get interesting. Because the HTTP spec
says that when you create a resource from a POST you are supposed to return a
Created HTTP status code and include a location to the new resource, the
method we want to test does some funny things to get the HttpResponseMessage
assembled.
My first attempt at a test looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Unfortunately, that didn’t work. You end up getting a NullReferenceException
thrown by Request.CreateResponse because it expects a fair amount of web
config stuff to have been assembled. This is a bummer, but it is what it is.
I reached out to Brad Wilson for help, and we figured out how to test this
without going all the way to creating a web server/client pair, but there is
clearly a lot of extra non-test code still running. We had to assemble a whole
bunch of interesting configuration and routing classes to make the
Request.CreateResponse method happy, but it did work.
The first test we wrote looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
In a future post, I may take a look at how we might use Visual Studio 2010 Fakes to create a Shim to remove all that config stuff, but this will have to do for now.
Since I knew I needed to make a few more tests to adequately pin the behavior
of PostProduct, I refactored out the ugly config code into a private method
in the test class called SetupControllerForTests. I find that when I have
issues like this, I really like to keep the weird setup code close to the
tests. I generally prefer this over creating abstract test classes because I
like it to be very obvious what is happening. I also like my tests to be easily
read and understood without having to jump around in the class hierarchy.
1 2 3 4 5 6 7 8 9 10 11 | |
Now that I have the helper method, I can refactor the status code test, and
add two more to check the location and to confirm that it actually calls the
AddProduct method on the repository.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
Conclusion
And now we’re done. We have successfully “pinned” the behavior of the entire
ProductsController class so if we later need to refactor it, we have a way of
knowing what the current behavior is. As I discussed in
my previous post about VS 2012 Shims, we can’t refactor without being able
to confirm the current behavior, otherwise we will end up in a Catch-22.
Creating “pinning” or “characterization” tests like those created here are the
first step to being able to safely and confidently refactor or add new
behaviors.
Hopefully this post showed you a few new things. First, we got to see another
example of using Stubs in unit tests. Also, we learned a bit about how to deal
with the nastiness around the HttpRequestMessage.CreateResponse extension
method.
Personally I wish that POST handler was as easy to test as the rest of the controller was. One of my favorite things about MVC was always that the separation of concerns let me test things in isolation. When a controller doesn’t have tight dependencies on the web stack, it is a well-behaved controller. Unfortunately, when you want to create a controller that followed the HTTP spec for POST, you will find this a bit hard today. But at least we found a way around it.
Let me know what you think!