Unit Test Isolation for Legacy .Net Code
August 6, 2014
Isolating code from dependencies is crucial for developing small, well-defined, easy-to-understand tests. And it is an absolute must when those dependencies call external resources, such as a database, filesystem, or heavy-duty component (e.g. for interacting with office docs). But how do you introduce isolation in new unit tests for legacy .Net code? Well, that depends... and I have a flow chart and brief notes to help you figure it out.
Stubs, Mocks, Etc.
Moq is my favorite framework because it is free and is relatively easy to understand (if you are comfortable with lambda expressions). No doubt RhinoMock, TypeMock (Isolator Basic), the Nmocks, and many other unnamed frameworks have their uses. Microsoft's Fakes framework can work for you – if you have an MSDN Premium or Ultimate license and you are confident that you won't downgrade at any point. My other problem with Fakes is listed as a virtue elsewhere in this post – it can easily be misused to substitute for good design. See chapter 20 of Growing Object-Oriented Software, Guided by Tests for more on the problem of mocking concrete classes.
And please don't forget, Mocks are not stubs.
At first glance it might seem that calls to static and extension methods are going to be a big thorn in your side. Thankfully Static Delegate Injection can easily solve that problem for you. I wrote about this in Making Mockery of Extension Methods.
Sprout or Wrap
Credit to Michael C. Feathers, Working Effectively with Legacy Code *
Let's say that you have a class that makes a database call, and you need to add some logic to validate the input. Add that validation in a new, protected method. In your unit test, create a test-specific subclass* that exposes the protected method as a public one. Write a test case using the test-specific subclass. This is Sprouting.
On the other hand, perhaps it will make sense to take the ADO.Net or ORM-related code out of your class, moving it to an Adapter class with a separated interface. Now you can take full advantage of mocking. This is Wrapping.
* For a bit more from Feathers without reading the whole book, see his 12 page article with the same title. Phil Haack wrote about test-specific subclasses from a .Net perspective: Test Specific Subclasses vs Partial Mocks.
When you really can't do anything about the time pressure and you and you can't make headway with the methods above, then you might need to break down and buy TypeMock or use Microsoft's Fakes framework. I have not evaluated the TypeMock product, but I think it uses the same techniques as Microsoft to break apart those nasty dependencies: Reflecting over methods and replacing them with new ones at runtime. It is almost a form of Aspect Oriented Programming, in my mind. These techniques can be brittle and hard to read. But in a pinch, they can get the job done.
Microsoft article on "shims" technology: Using shims to isolate your application from other assemblies for unit testing
TrackBack URL: http://www.safnet.com/fcgi-bin/mt/mt-tb.cgi/130