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!
[tags: Agile, TDD, .NET]
Add this to the AssemblyInfo.cs of the tested code:
[assembly:
InternalsVisibleTo("DynamicAssemblyProxyGen,PublicKey=0024000004800000940000000602000000240000525341310004000001000100FB4FF5A7C8BBA6FEB6A6B75B260CD57C1B8B85B63A45DEDCB7081331740C870852AF30ABD2A74700CCE1D7A01AEED019339DB202E010AC808396B2922362877C6AFC8993281092434A223B9920CAC8BA409D138A97B73CD1BAAD813AF450B886E3D7F5A09EE450D415145EB0524778A6BD1AE733FD2B6CEEBFD151620534BCB7")]
Karl,
Just a thought, you probably want to be mocking the call to HttpRuntime.Cache.Insert() as well, or at least do something to verify that you’re putting the data into the cache correctly. You’ll probably want to create an abstracted interface to wrap the HttpRuntime.
Thanks for this. I just started trying out Rhino Mocks myself, and this has helped a lot. I was confused by the terminology, specifically “ReplayAll”, which really doesn’t really replay anthing. It might be better named something like “BeginTest” or “StartMonitoring”.
By the way RhinoMocks does support generics, so you should be able to do something like:
IDataStore store = mock.CreateMock();