A Simple DataTable Constraint for RhinoMocks

February 7, 2008

I’ve been a huge fan of Rhino Mocks since I heard about it on a way-back-episode of Dot Net Rocks HanselMinutes. One of the more awesome features of Rhino Mocks is that it utilizes a Record/Playback semantic so that you can set up your mocks with actual calls to the mock objects, then play them back in your tests. This is great if you rely on intellisense when calling your object instances, or if you use Resharper or another code refactoring tool.

Recently, I trying to use Rhino Mocks with DataTables like so:

DataTable a = new DataTable();
DataTable b = new DataTable(); 

MockObject mock = mockRepository.CreateMock<MockObject>();

using (mockRepository.Record())
{
   Expect.Call(mock.CreateRecord("TableName", a, b).Return(1);
}
using (mockRepository.Playback())
{
  MyObject obj = new MyObject(mock);
  obj.Save();
}

When I ran the test, Rhino Mocks says that a the DataTable created during the obj.Save() call wasn’t the same as the one expected. This was correct as there were now two instances, and it seems that the default check Rhino Mocks performs is an Object.Equals(). Since I needed to do some more deep comparisons of the Expected and Actual DataTables, a fellow developer and I started a very simple DataTableConstraint:

    class DataTableConstraint : AbstractConstraint
    {
        private readonly DataTable _expected;
        private string _errorMessage;

        public DataTableConstraint(DataTable expected)
        {
            _expected = expected;
        }

        public override bool Eval(object obj)
        {
            DataTable actual = obj as DataTable;
            if (actual == null)
                return false;

            if (!CheckColumns(actual))
            {
                return false;
            }
            if (!CheckData(actual))
            {
                return false;
            }
            return true;
        }

        private bool CheckData(DataTable actual)
        {
            if (actual.Rows.Count == 0)
            {
                _errorMessage = "Actual table has no data to compare";
                return false;
            }
            try
            {
                for (int i=0; i < _expected.Rows.Count; i++)
                {
                    foreach (DataColumn column in _expected.Columns)
                    {
                        object expectedCell = _expected.Rows[i][column];
                        object actualCell = actual.Rows[i][column];

                        if (expectedCell != actualCell)
                        {
                            _errorMessage = string.Format("Expected {0} in Row ({1}), Column ({2}), but was {3}", expectedCell, i, column.ColumnName, actualCell);
                            return false;
                        }
                    }
                }
                return true;
            }
            catch (System.Exception e)
            {
                _errorMessage = e.Message;
                return false;
            }
        }

        private bool CheckColumns(DataTable actual)
        {
            foreach (DataColumn column in _expected.Columns)
            {
                if (!actual.Columns.Contains(column.ColumnName))
                {
                    _errorMessage = string.Format("Could not find column {0} in {1}", column, actual);
                    return false;
                }
            }
            return true;
        }

        public override string Message
        {
            get { return _errorMessage; }
        }
    }

To use this using the above sample code, do

Expect.Call(mock.CreateRecord(null, null, null).Constraints
(
  Is.Anything(),
  new DataTableConstraint(a),
  new DataTableConstraint(b)
);

Now, this is just a start, and will probably evolve more as I start to get more into verifying the data in two tables. Perhaps Oren will create a new Syntax Helper along the lines of Data.Equals(expectedDataTable) since using a new Constraint() call is a little awkward.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: