Friday, January 29, 2016

Mocking the Database

I believe I mentioned before that I worked with a custom, home-grown ORM at one of my clients.  If I didn't mention that, I'm mentioning it now.  One of the biggest problems I had with the solution we implemented was that we didn't have any tests for it.  Every now and then we'd encounter a new scenario (like returning a list of objects instead of a single object) and we'd have to code it in, with no way to make sure we weren't breaking what was already there.

One (particularly slow) day I decided enough was enough and I set out to create automated unit tests for every method in that behemoth.  That's when I encountered the problem: how do you mock a database for consumption by ADO.NET?  It turns out it's pretty easy and pretty straightforward.  First off, I found this code somewhere else (which, I know, technically violates the name of the blog, but it is what it is).  You can check out the original post here if you're interested.  I had to make a few changes for my version of MOQ and my specific circumstances, but that's the blog that got me started down the right path.

Here's the high level overview of the ORM: go get data using a DbDataReader, read through the columns in the reader, and for each column, find a corresponding property on the object and map the value from the reader to the property.  It's honestly pretty straightforward.  All I had to figure out was how to mock a DbDataReader.  Here it is:


   1:  private Mock<DbDataReader> CreateFakeDbDataReader(int numberOfFields, int numberOfReads = 1,
   2:              MockBehavior mockBehavior = MockBehavior.Loose, bool hasRows = true)
   3:  {
   4:      // create a mock repository (database)
   5:      var repository = new MockRepository(mockBehavior);
   6:      // create a reader for the mocked repository
   7:      var moqReader = repository.Create<DbDataReader>();
   8:      // setup the reader so that it always has rows (or indicates that it has rows anyway)
   9:      moqReader.SetupGet(p => p.HasRows).Returns(hasRows);
  10:      // setup the reader to indicate it has the specified number of fields
  11:      moqReader.SetupGet(p => p.FieldCount).Returns(numberOfFields);
  12:   
  13:      // readCounter is used to allow the reader to be iterated
  14:      // incrementing readCounter in the callback allows the reader to be read a specific number of times
  15:      var readCounter = 0;
  16:      moqReader.Setup(x => x.Read()).Returns(() => readCounter < numberOfReads).Callback(() => readCounter++);
  17:   
  18:      return moqReader;
  19:  }

When I want to mock a result set that has only one row I use this:
   1:  private DbDataReader CreateDataReaderWithSingleResultSet(Dictionary<string, object> values,
   2:              int numberOfReads = 1, bool hasRows = true)
   3:  {
   4:      // get the basic fake database reader
   5:      var moqReader = CreateFakeDbDataReader(values == null ? 0 : values.Count, numberOfReads, hasRows: hasRows);
   6:   
   7:      if (values == null)
   8:      {
   9:          return moqReader.Object;
  10:      }
  11:   
  12:      // iterate the objects (fake data)
  13:      for (var i = 0; i < values.Count; i++)
  14:      {
  15:          var item = values.ElementAt(i);
  16:          // setup the reader to return the name of the "field" when the index is used on GetName
  17:          moqReader.Setup(p => p.GetName(i)).Returns(item.Key);
  18:          // setup the reader to return the value when it encounters the key
  19:          // this is where we specify that if reader["FirstName"] is evaluated, "Fake" (or whatever) will be returned
  20:          moqReader.SetupGet(p => p[item.Key]).Returns(item.Value);
  21:          moqReader.SetupGet(p => p[i]).Returns(item.Value);
  22:      }
  23:   
  24:      return moqReader.Object;
  25:  }

And when I want to mock a result set that has multiple rows I use this:

   1:  private DbDataReader CreateDataReaderWithSingleResultSetWithMultipleRows(List<Dictionary<string, object>> rows,
   2:              bool hasRows = true)
   3:  {
   4:      // get the basic fake database reader
   5:      var moqReader = CreateFakeDbDataReader(rows.First().Count, rows.Count, hasRows: hasRows);
   6:   
   7:      var currentRow = 0;
   8:   
   9:      // iterate through the "rows" of data
  10:      for (var i = 0; i < rows.Count; i++)
  11:      {
  12:          // for each "row" in the data, check if the current row is being retrieved
  13:          if (i != currentRow)
  14:          {
  15:              continue;
  16:          }
  17:   
  18:          var row = rows[i];
  19:          // iterate through the "fields" in the current row
  20:          for (var j = 0; j < row.Count; j++)
  21:          {
  22:              var item = row.ElementAt(j);
  23:              // setup the reader to return the name of the "field" when the index is used on GetName
  24:              moqReader.Setup(p => p.GetName(j)).Returns(item.Key);
  25:              // setup the reader to return the value when it encounters the key
  26:              // this is where we specify that if reader["FirstName"] is evaluated, "Fake" (or whatever) will be returned
  27:              moqReader.SetupGet(p => p[item.Key]).Returns(item.Value);
  28:          }
  29:      }
  30:   
  31:      return moqReader.Object;
  32:  }

The one part that I never got coded because I didn't really need to was returning multiple result sets.  I'm sure it can be done, but I haven't had to do it yet.  Happy coding!

No comments:

Post a Comment