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