The code for these intrductory articles is from a simple RSS system. There are three parts to the system - a datastore (IRSSDataStore) where a list of feeds are kept, an aggregator (IRSSAggregator) which is responsible for updating the feeds, and a simple consumer (SimpleRSSSystem).
Our IRSSAggregator interface looks like this.
public interface IRSSAggregator
{
IEnumerable ShowLatestMessages();
int UpdateIntervalMinutes();
int MinutesUntilNextUpdate();
DateTime LastUpdate();
}
And our simple consumer looks like this
public class SimpleRSSSystem
{
private readonly IRSSAggregator _Aggregator;
public SimpleRSSSystem(IRSSAggregator aggregator)
{
_Aggregator = aggregator;
}
public string DisplayCountdownMessage()
{
int update = _Aggregator.MinutesUntilNextUpdate();
if (update == 0)
{
return "Updating now...";
}
else if (update == 1)
{
return "1 minute until next update";
}
else
{
return update + " minutes until next update";
}
}
}
We want to test to ensure that the correct message is being returned. Using a real world implementation of the aggregator, this would be difficult to do. We can assume that a real aggregator would have some kind of internal time which would trigger the updates. In order to test a real instance of this we would need to expose the timer's interval property and manipulate its data. As you can imagine, in a non-trivial example this would not only lead to a sloppy API, but would also expose far more of the class than we should be comfortable with.
Enter mock objects. The purpose of a mock object is to allow you to test the interactions between components when one or more of those components does not lend itself easily to state based testing. In this scenario, our aggregator is an ideal candidate for mocking.
We have three possibilities that we want to test: Zero, one, or more minutes remaining on the Aggregator until it is going to be updated.
Lets set up a test case for zero.
[Test]
public void CorrectMessageIsDisplayedForZeroMinutes()
{
MockRepository mockery = new MockRepository(); //Line 1
IRSSAggregator rssGetter = mockery.CreateMock(); //Line 2
string message;
using (mockery.Record()) //Line 4
{
Expect.Call(rssGetter.MinutesUntilNextUpdate()).Return(0); //Line 5
}
using (mockery.Playback()) //Line 6
{
SimpleRSSSystem system = new SimpleRSSSystem(rssGetter); //Line 7
message = system.DisplayCountdownMessage(); //Line 8
}
mockery.VerifyAll(); //Line 9
Assert.That(message.Contains("Updating now")); //Line 10
}
Walking through what we have done here.
Line 1: We create an instance of Rhino.Mocks.MockRepository which is the "brain" of the system.
Line 2: We tell Rhino Mocks to create a mock version of our IRSSAggregator. Internally, Rhino Mocks creates a dummy class that implements our interface.
Line 4: We tell Rhino Mocks to start recording our expectations. What this means is that we are saying "hey, Rhino - write this down - I expect these methods to be called, and when they are I expect these values to be returned".
Line 5: We are telling Rhino Mocks that the MinutesUntilNextUpdate method is going to be called, and that when it is it should return Zero. At first, there seems to be a slight disconnect here. We expect that the method will be called at some point during the test, but we also expect that it will return zero. The first expectation is self explanatory -- we have told Rhino Mocks that this method will be called. The second expectation is not so clear. What we are saying here is that we expect the inernal workings of Rhino Mocks to make sure that a zero is returned when that method is called.
Line 6: We tell rhino mocks that we are entering "playback" mode. This is where we will do operations that need to be verified.
Line 7: We create an instance of our SimpleRSSSystem, passing in the mocked IRSSAggregator.
Line 8: We call the DisplayCountDownMessage method, which internally calls the MinutesUntilNextUpdate method on our mocked RSSAggregator. Because this code is deliberately simple, we know that our first expectation (that MinutesUntilNextUpdate) has been met. We can also safely assume that our second expectation (MinutesUntilNextUpdate returns zero) has also been met.
Line 9: We move beyond our assumptions and verify that our expectations were indeed met. Rhino Mocks created our instance of IRSSAggregator, and as a concerned parent, kept track of what we did with it.
Line 10: We call and NUnit assert to check that the message we were expecting was returned.
And that, in a nutshell, is about as simple an introductory example as is possible.