I’ve been playing around with mock objects using Rhino.Mocks for the last few months, but today was the first time that I had an unfettered coding win and became a true convert.
I was introduced to NUnit about five years ago from a co-worker (thanks Dewayne) and gradually became a true believer.
Unfortunately, I was a late-bloomer in the area of mocking, so by the time I finished my last big project I had left a suite of about 500 database driven tests that took almost 2 hours to run and were a maintenance nightmare. The vast majority of my unit testing efforts revolved around SQL initialization and cleanup scripts that massaged the database into just the state I needed for each tests case scenario.
Although this approach definitely made me a wizard when it came debugging data-related production issue quickly, it was too high of price to pay for the benefits I was receiving. Although I remember thinking in the beginning that this “realistic” approach to testing was much more valuable than the “fake” tests that didn’t hit the database, by the end of the process I was no longer so sure. I still believed in the value of unit testing, but I knew there had to be a better way of doing it.
During my stint as an architect, I read enough about mocking to know that it was a good idea in theory, but of course I had little opportunity to put that theory to the test. Now that I am a code monkey again, I can say with certainty that mocking is an absolutely essential aspect of Test Driven Development and not simply a “nice-to have” supplemental activity like I assumed before.
Why is Mocking Critical?
- It makes debugging easier - When a database driven test fails, the problem could be anywhere and in my experience it is most often related to a problem with the initialization script (i.e. accidental order-based test dependencies). This often led me to ignore test failures when I was pressed for time because I knew that it was likely just another false alarm. However, when a test that uses mock objects fails, I am not only fairly certain that it is a problem with the code rather than the test, but I also know exactly where the problem is because I’ve guaranteed the behavior of every piece of code except the part that I am trying to test.
- It makes writing tests faster and easier – One of the biggest barriers to adoption for TDD for most developers continues to be the perception that it takes too long. I can give a dozen reasons why that perception is wrong, but in the end I think it is more productive to simply find ways to shorten that process. That is exactly what mocking does. Writing out a series of Expect.Call() statements is an order of magnitude faster than trying to write data manipulation statements.
- It makes tests execute faster – Don’t underestimate the power of fast feedback. Even if you are not a die-hard continuous integration practitioner, you have to admit that the longer your tests take to run the less likely you and other developers will be to actually run them. Even if you do religiously run them at night, the longer feedback cycle dramatically decreases one of the main benefits of TDD, which is the increased coding speed that comes from test-backed confidence and not having to waste time on excessive analysis and fretting about possible unknown collateral damaged.
What does Mocking Require?
- Interface Based Programming – If you use Rhino.Mocks, then every class that you mock must implement an interface. With the help of ReSharper, interfaces can be created from an existing class and propagated to the rest of your codebase with just a few keystrokes. This alone makes the price of ReSharper worth it if you are going to start using mocking in a Legacy system that was designed without using interfaces.
- Dependency Injection – One of the first stumbling blocks I ran into came from complex collaborations from objects that weren’t able to be mocked because they were created inside of methods. You can quickly get around this with a little refactoring by making collaborating objects private member variables and then adding a constructor overload to expose them during testing. It is usually a relatively fast and low-risk design change to make.
- Identifying Code Seams – If you find yourself getting frustrated and beginning to think that you are stuck in a code base that is impossible to mock, then stop and read the book Working Effectively With Legacy Code by Michael Feathers. I’m about a quarter of the way through this right now and it is one of the most helpful tech books I’ve ever read. The author is fearless and infinitely resourceful when it comes to getting code of any language and any quality under test. In fact, many of his examples are in C++ and even C, so that means that you have no excuses when it comes to your own project. He gives lots of practical advice on how to find the “seams”, which are the easiest and most cost-effective places to modify code so that you can begin to mock it.
A Practical Example?
Here is an MbUnit test that I wrote today before I tackled a production bug that was assigned to me. It mocks the IPaymentTypeItem, which has read-only properties and thus is normally only allowed to be populated from the database. The Expect.Calls() are just done for the methods and properties calls that are actually made on the object in the piece of code I am testing. You can get a full tutorial with better explanations of the API calls here.
2: [Row("%B409999999999999^Russ's Coffee Emporium^09011211000019900000000?;","409999999999999")]
4: public void Should_Load_Correctly_With_Partial_Track(string trackData, string expCardNumber)
6: MockRepository mocks = new MockRepository();
7: IPaymentTypeItem payment = (IPaymentTypeItem) mocks.DynamicMock(typeof (IPaymentTypeItem));
12: FinancialCardEncoding encoding = FinancialCardEncoding.CreateCardEncoding(trackData,payment,true);
Rhino.Mocks is certainly not the only mocking framework out there, but so far I like it because it takes a strongly typed approach rather than relying on strings which means I get to use intellisense. Whatever framework you do choose, I highly recommend that you take the time to learn it well and incorporate it thoroughly into your development as soon as possible.
Popularity: 15% [?]