UPDATE: I solved my problem by marking my method as protected internal instead of just internal.
My adventures through TDD land aren't over yet. I decided to follow
Joshua's and
Jeremy
's advice and spend some time looking into RhynoMocks.
As you may recall, in Lesson 3 I decided to start testing near my
database boundary by using a factory provider and creating a fake/test
implementation. Basically, instead of actually hitting a database, I
would store values in memory. It worked really well, but it did require
a lot of work - and sometimes I had to spend time thinking about and
planning my fake implementation. I now realize that on top of the
extra work, I was also testing a lot more than just "units" of code.
So I downloaded
RhinoMocks.
There's definitely a small learning curve - not sure if it's technical
or mental. After a bit of playing with it, I'm really getting into it.
So sticking with the example we've been working on, let's change our
DataProvider to a concrete class which relies on an IDataStore
implementation to do the database interaction. Our DataProvider now
looks something like:
public class DataProvider
{
private IDataStore _store;
public DataProvider()
{
//this is our actual store, which we default to
_store = new SqlDataStore();
}
internal DataProvider(IDataStore store)
{
//override the default store...say for testing!
_store =store;
}
public List<Indicator> GetAllIndicators()
{
List<Indicator> indicators = _store.GetAllIndicators();
if (indicators == null)
{
indicators = new List<Indicator>();
}
return indicators;
}
}
Looking at GetAllIndicators (yes, I wrote the code BEFORE writing the
test, *tisk*), I see at least 2 things I want to test - the store's
GetAllIndicators() is called and null is never returned. At first I
thought I wanted to test more than just the interaction between my
DataProvider and the store, but I quickly realized that (a) my
TestDataProvider didn't really test anything more since the
implementation isn't real and (b) I'm unit testing DataProvider, not my
store.
So I write my unit test:
[Test]
public GetAllIndicatorsReturnsEmptyListInsteadOfNull()
{
MockRepository mock = new MockRepository();
IDataStore store = (IDataStore)mock.CreateMock(typeof(IDataStore));
//inject my mock object
DataProvider provider = new DataProvider(store);
Expect.Call(store.GetAllIndicators()).Returns(null);
mock.ReplayAll();
Assert.IsNotNull(provider.GetAllIndicators());
mock.VerifyAll();
}
I create a mock implementation of IDataStore - as you can see, I've
already gained a lot by not having to implement my own test version. I
then say that I expect a call to my mock object and that it ought to
return null. Then I go into replay mode, actually execute the code I
want to test and verify my expectations.
Now, suppose I wanted DataProvider.GetAllIndicators to cache the
results (I know, i know..caching has it's ups and downs...). I would
only expect 1 call to store.GetAllIndicators..so I write my test:
[Test]
public GetAllIndicatorsIsCached()
{
MockRepository mock = new MockRepository();
IDataStore store = (IDataStore)mock.CreateMock(typeof(IDataStore));
//inject my mock object
DataProvider provider = new DataProvider(store);
Expect.Call(store.GetAllIndicators()).Returns(new List<Indicator>());
mock.ReplayAll();
Assert.IsNotNull(provider.GetAllIndicators());
Assert.IsNotNull(provider.GetAllIndicators());
mock.VerifyAll();
}
The only things I've changed are:
- Return new List<Indicator>() instead of null (not really necessary), and
- Call provider.GetAllIndicators TWICE
I run the test and it fails. I get an error saying
store.GetAllIndicators was expected to be called once, but it was
called twice. So I go into my code and add the caching logic:
public List<Indicator> GetAllIndicators()
{
string cacheKey = "DataProvider:GetAllIndicators";
List<Indicator> indicators = (List<Indicator>)HttpRuntime.Cache[cacheKey];
if (indicators == null)
{
indicators = _store.GetAllIndicators();
if (indicators == null)
{
indicators = new List<Indicator>();
}
HttpRuntime.Cache.Insert(cacheKey, indicators, null, DateTime.Now.AddMinutes(10), Cache.NoSliddingExpiry);
}
return indicators;
}
And my test now passes because _store.GetAllIndicators is only called once.
All in all, I'm very impressed. I was having a problem mocking a class
object that had an internal virtual member. I did use [assembly:
InternalsVisibleTo(RhinoMocks.StrongName)], but as soon as I tried to
create my mock object, I was getting an exception deep inside
RhinoMocks about implementing or overriding a member that wasn't
visible/accessible...I don't remember the exact message, but if anyone
can help me out, I'd appreciate it!
Technorati Tags: Agile, TDD, .NET