Today when modifying what we call an “order notifier” (essentially observers that are notified when an order is placed), I was having trouble figuring out why my test was failing. The project is written in C# and this test was using Rhino Mocks to isolate the EmailService class. We obviously don’t want to test our code with an actual email implementation, so a mocking/isolation framework is a perfect tool for abstracting this out and making testing easier.
The TestFixture and Test I Wrote
Below is a very verbose version of the test class using NUnit. The actual tests use some shared methods for setting up the order and making assertions on the email service. I opted to show more code just so it’s easier to see all the setup and verification that was going on.
The only additions to this test were those asserting that the email’s message body contained the customer number and the order number. With these new expectations set up on the CustomerServiceNotifier, it was time to write the code to make that expectation valid.
The Notifier Code
Some of this code was removed because it’s either not important to this post, or just too lengthy. This is a trimmed down version of the actual implementation.
The notifier was changed to format the “needs shipping quote” message to contain the order number and the customer number. If you’ve already spotted the mistake, nice work, I didn’t find it so fast.
The failure stated the following:
IEmailService.SendOrderReceived(anything, body => ((body.Contains(“This order needs a shipping quote.”) && body.Contains(1337.ToString())) && body.Contains(2401.ToString()))); Expected #1, Actual #0.
I knew that the method was being called. I couldn’t figure out why it was giving me the error message at first. I decided to take advantage of Rhino Mocks’s WhenCalled method. This allows you to provide a delegate to do some fancy stuff when that method is called. I just decided to use some simple output so I could see the actual string that was being passed to the mock email service. That was done pretty easily:
After I generate my mock, I just stub out the method, ignoring any arguments, and output the second argument to the console. I re-ran the test and it gave me the output like so:
<br />This order needs a shipping quote:<br />Order #: 2401<br />Cust #:1337
It only took me one look to see the problem and scream think: Doh!! The actual text contains a colon character after “quote” instead of my test which was expecting a period character. The character was changed, I removed the WhenCalled delegate set up and I was back to good. This idea seemed like a nice way to do some debugging without having to slowly step through the code.
Some Thoughts on the Problem
I’m all for writing good tests and adhering to best practices and while my unit test above technically only contains only one assertion from Rhino Mocks (a good idea), it’s actually making 3 assertions total (not so good). I’m checking that the email body contains some text, an order number and a customer number. I really should break this up into 3 separate tests since it’s checking 3 requirements within the email body. I guess I have something to do tomorrow morning. :)
Some Thoughts on Rhino Mocks WhenCalled Method
I’ve found myself using this more and more lately when stubbing out test setup. Primarily when using the WhenCalled method I’ve been thinking of it in my head as a “pass-through”. I want to ask for an object given some specific data and get back a fake object of that type that matches on the value I passed in. I’m not sure if the thought in my head is making sense, so I’ll give some sample code.
The following IScheduleResolver interface is meant to provide an ISchedule back when called. I don’t really want to set up a new fake object for all the variations passed in, I just want to return a schedule that is valid (or possibly invalid) for that time I provided. Here’s the setup of this idea:
Essentially what this is saying:
No matter what IClock value you pass into GetCurrentSchedule, I’m going to return a fake ISchedule that is valid for that IClock.
You could do the opposite of this too, of course. This has been very helpful in reducing some extra set up I might have otherwise endured during testing.