Mocking the TableGateway pattern

I've been writing some new Hands On Labs for the WCF REST Starter Kit and as I mentioned previously on my blog I want to include unit tests.  Several people commented on my previous efforts and pointed out that the tests I included with the REST Collection lab are really integration tests because they use the HTTP stack to pass messages from a client to the REST Service.

So this time I've decided to build out both an integration test layer and a unit test layer.  My goal for the unit test layer was to exercise the service without the HTTP stack or the database.  What this means is that I had to come up with a way to mock the database layer.

In my previous code I was using static methods on the CohoDB class backed up by LINQ to SQL classes.  This time I took a new approach that uses the Table Gateway pattern with a twist.  I wanted to use the Factory pattern with the gateway and provide a way to substitute the factory with a Mock factory that I could use when unit testing.  Sound confusing?  Here is the pattern.

First you need to define an interface for the TableGateway

     public interface IWineGateway
    {
        WineData GetWine(int wineID);
        IEnumerable> GetWines(int startIndex, int pageSize);
        IEnumerable> GetAllWines();
        WineData AppendWine(WineData wineData);
        WineData UpdateWine(int wineID, WineData wineData);
        WineData PutWine(int wineID, WineData wineData, out bool inserted);
        void DeleteWine(int wineID);
    }

Next you need an interface for the Factory

     public interface IWineGatewayFactory
    {
        IWineGateway CreateWineGateway();
    }

Now you need to implement your interfaces, the factory and the table gateway with classes. To make it simple to create the table gateway I use a static property that defines the factory that will be used (it defaults to the factory that creates the real class) and a static method to create the gateway.

     public class WineGateway : IWineGateway
    {
        static IWineGatewayFactory _factory;

        public static IWineGatewayFactory Factory
        {
            get
            {
        // Default to the WineGatewayFactory
                if (_factory == null)
                {
                    _factory = new WineGatewayFactory();
                }

                return _factory;
            }
            set
            {
        // Unit tests can set a MockFactory if they like
                _factory = value;
            }
        }

        public static IWineGateway Create()
        {
            return Factory.CreateWineGateway();
        }

Now when I need a table gateway I can call the static Create method and get one. However it still wasn't quite as convenient as I wanted so in the consuming service I added a property of type WineGateway that automatically created it when I needed it.

         IWineGateway _winesGateway;

        IWineGateway WinesGateway 
        {
            get
            {
                if (_winesGateway == null)
                    _winesGateway = WineGateway.Create();

                return _winesGateway;
            }
        }

This allows me to make calls into the table gateway with one simple line of code, just like I did before using the static methods on CohoDB

         protected override WineData OnGetItem(string id)
        {
            Int32 wineID;

            if (!Int32.TryParse(id, out wineID))
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest);
            }

            return WinesGateway.GetWine(wineID);
        }

Beautiful isn't it? But wait there's more! What about when unit testing? No problem, just inject the Mock factory instead and create a class that implements the gateway interface

         [TestInitialize()]
        public void MyTestInitialize()
        {
            // Setup the WineGateway to use the mocks
            WineGateway.Factory = new MockWineGatewayFactory();
        }

Now my project has both integration and true unit tests. I haven't posted it on the web yet because I've more cleanup work to do and need to finish writing the docs but it's going to be great!