One of the threads I was following on twitter last week was an exchange between a Microsoft architect and a unit tester. The tester was complaining about the lack of interfaces around a lot of the .net classes, especially in new features. The Microsofty countered with the increased risk of people rolling their own implementation of critical functionality and the increased demand on support that would produce. As someone who’s had to write “test shells” around .NET, I can definitely empathize with the tester.
Let’s say you have some code like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
… and you want to write some unit tests for it. There’s four situations you want to deal with here: returning a string of even length, returning a string of odd length, throwing a WebException with a status code of InternalServerError, and all other exceptions. I’m only going to deal with two of them: returning a string of even length and throwing an “InternalServerError” web exception.
Mocking the Unmockable
So step one to unit testing – mock out your external dependencies. Usually you can do this with a mocking framework, but most of those frameworks work with interface and/or abstract class definitions. You don’t get those with a noticable portion of the .NET framework – in this case, there is no IWebClient interface or WebClientBase abstract class. So in order to mock out your web client dependency, you have to create something that you can actually mock. An example could be:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
As you can see, we’re basically creating a shim around the WebClient functionality simply so we can mock it out. This is bad for two reasons: we have to do it, and we’re now inserting “test-oriented” code into your production code, which is generally a bad idea. Unfortunately, there’s not a lot of other ways around it – if there is a better one, please let me know.
Fortunately, this sample is pretty straightforward – we’re only wrapping a method that takes a string and returns a string, so nothing complicated is going on. What would the unit test look like? (Using FakeItEasy and FluentAssertions)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Pretty standard. Now it’s testable, and it would be trivial to write a unit test for the odd string length case.
Down the Rabbit Hole
But what about the exception? Here’s where it gets even trickier because of another design decision from .NET: you can’t just create a HttpWebResponse. It requires a bunch of serialization info and whatnot to be instantiated. Even if you did create one, a lot of the properties are read only, including the HttpStatusCode property. So how can you create a unit test that relies on HttpStatusCode being set, without actually making a web request? You need to add even more wrappers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
This wrapper allows us to “fake” a HttpStatusCode, which is exactly what we need to do to simulate the InternalServerError in our unit test. It also needs to work in production, so we have to have a way of passing an actual HttpWebRequest in and access it. But now we have to change our MyWebClient class to throw one of these fancy custom MyHttpWebResponse classes on web exceptions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Confused yet? I am, and I’m the one writing the code! The net result is something you can test, which looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
So, all that extra code, all that in production test code, just to be able to write this little test that most other frameworks have no problem with.
There’s some cleaner ways of doing this. For example, you could use some sort of dependency injection to sub out your real classes for test ones to avoid having both test code and production wrappers in the same class; inject the test implementation when running tests, and use production wrappers in production. But even then, you still need wrappers, which sucks.
I understand that there’s legacy issues, backwards compatibility, etc. when it comes to things like this, especially in a ecosystem like the .NET framework and a critical class like WebClient. But when you release new functionality and don’t include some of this stuff it gets pretty annoying. As to the other arguments that were made about breaking compatibility, support issues, etc – the extra cruft we have to add just to get to a unit testable state introduces similar pain points, and at a higher cost to the developer.
PS) Please, please, if there is a better way of doing this, let me know.