CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Karl Seguin

.NET From Ottawa, Ontario - http://twitter.com/karlseguin/

TDD Lesson 4 - I mock you (updated)

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: , ,



Published Aug 15 2006, 10:16 AM by karl
Filed under:

Comments

Jeremy D. Miller said:

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.
# August 15, 2006 5:09 PM

anonym said:

Add this to the AssemblyInfo.cs of the tested code:

[assembly:

InternalsVisibleTo("DynamicAssemblyProxyGen,PublicKey=002400000480000094000­0000602000000240000525341310004000001000100FB4FF5A7C8BBA6FEB6A6B75B260CD57C­1B8B85B63A45DEDCB7081331740C870852AF30ABD2A74700CCE1D7A01AEED019339DB202E01­0AC808396B2922362877C6AFC8993281092434A223B9920CAC8BA409D138A97B73CD1BAAD81­3AF450B886E3D7F5A09EE450D415145EB0524778A6BD1AE733FD2B6CEEBFD151620534BCB7"­)]

# February 13, 2007 6:46 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add
Check out Devlicio.us!

Our Sponsors

Free Tech Publications