如何模拟 IDataReader 以测试将 SqlDataReader 转换为 System.DataView 的方法
How to mock IDataReader to test method which converts SqlDataReader to System.DataView
我是 Moq 的新手,我正在努力编写单元测试来测试将 SqlDataAdapter
转换为 System.DataView
的方法。这是我的方法:
private DataView ResolveDataReader(IDataReader dataReader)
{
DataTable table = new DataTable();
for (int count = 0; count < dataReader.FieldCount; count++)
{
DataColumn col = new DataColumn(dataReader.GetName(count),
dataReader.GetFieldType(count));
table.Columns.Add(col);
}
while (dataReader.Read())
{
DataRow dr = table.NewRow();
for (int i = 0; i < dataReader.FieldCount; i++)
{
dr[i] = dataReader.GetValue(dataReader.GetOrdinal(dataReader.GetName(i)));
}
table.Rows.Add(dr);
}
return table.DefaultView;
}
我正在尝试创建类似的东西:
var dataReaderMock = new Mock<IDataReader>();
var records = new Mock<IDataRecord>();
dataReaderMock.Setup(x => x.FieldCount).Returns(2);
dataReaderMock.Setup(x => x.Read()).Returns(() => records);
我想传递一些数据并验证它是否已转换。
谢谢。
你的模拟在正确的轨道上,但是 dataReaderMock.Setup(x => x.Read()).Returns(() => records);
是你出错的地方,因为 .Read
returns 一个 bool,而不是记录本身,它被读出IDataReader
用你的方法。
安排模拟:
var dataReader = new Mock<IDataReader>();
dataReader.Setup(m => m.FieldCount).Returns(2); // the number of columns in the faked data
dataReader.Setup(m => m.GetName(0)).Returns("First"); // the first column name
dataReader.Setup(m => m.GetName(1)).Returns("Second"); // the second column name
dataReader.Setup(m => m.GetFieldType(0)).Returns(typeof(string)); // the data type of the first column
dataReader.Setup(m => m.GetFieldType(1)).Returns(typeof(string)); // the data type of the second column
你可以安排列来模拟更多的真实数据,类型等。在你的系统中,只要确保第一个计数,GetName
的数量s 和 GetFieldType
的数量是同步的。
要排列 .Read()
,我们可以使用 SetupSequence:
dataReader.SetupSequence(m => m.Read())
.Returns(true) // Read the first row
.Returns(true) // Read the second row
.Returns(false); // Done reading
要在测试中使用它 你可以将它提取到一个方法中:
private const string Column1 = "First";
private const string Column2 = "Second";
private const string ExpectedValue1 = "Value1";
private const string ExpectedValue2 = "Value1";
private static Mock<IDataReader> CreateDataReader()
{
var dataReader = new Mock<IDataReader>();
dataReader.Setup(m => m.FieldCount).Returns(2);
dataReader.Setup(m => m.GetName(0)).Returns(Column1);
dataReader.Setup(m => m.GetName(1)).Returns(Column2);
dataReader.Setup(m => m.GetFieldType(0)).Returns(typeof(string));
dataReader.Setup(m => m.GetFieldType(1)).Returns(typeof(string));
dataReader.Setup(m => m.GetOrdinal("First")).Returns(0);
dataReader.Setup(m => m.GetValue(0)).Returns(ExpectedValue1);
dataReader.Setup(m => m.GetValue(1)).Returns(ExpectedValue2);
dataReader.SetupSequence(m => m.Read())
.Returns(true)
.Returns(true)
.Returns(false);
return dataReader;
}
(或者,您可以将其安排在 Setup
上,如果这对您的测试更有意义 class - 在这种情况下,dataReader
模拟将是一个字段,而不是返回值)
示例测试。然后它可以像这样使用:
[Test]
public void ResovleDataReader_RowCount()
{
var dataReader = CreateDateReader();
var view = ResolveDataReader(dataReader.Object);
Assert.AreEqual(2, view.Count);
}
[Test]
public void ResolveDataReader_NamesColumn1()
{
var dataReader = CreateDataReader();
var view = ResolveDataReader(dataReader.Object);
Assert.AreEqual(Column1, view.Table.Columns[0].ColumnName);
}
[Test]
public void ResolveDataReader_PopulatesColumn1()
{
var dataReader = CreateDataReader();
var view = ResolveDataReader(dataReader.Object);
Assert.AreEqual(ExpectedValue1, view.Table.Rows[0][0]);
}
// Etc..
(我使用过 NUnit,但它与测试方法上的不同属性和不同的断言语法类似,适用于不同的测试框架)
顺便说一句,我通过将 ResolveDataReader
更改为 internal
并设置 InternalsVisibleTo
来使上述工作正常进行,但我假设您有进入此私有方法的途径,因为您您已经尽力测试它了。
我的 class 设置 IDataReader
模拟:
public static class DataReaderMock
{
public static void SetupDataReader(this Mock<IDataReader> mock, ICollection<string> columns, object[,] values)
{
if (columns.Count != values.GetLength(1))
{
throw new ArgumentException($"The number of named columns must be identical to the number of columns in the 2d values array: {columns.Count} compared to {values.GetLength(1)}");
}
mock.Setup(reader => reader.FieldCount).Returns(columns.Count);
var setupSequence = mock.SetupSequence(reader => reader.Read());
var callbacks = new List<Action<object[]>>
{
vals => vals.Populate(columns.Cast<object>().ToList())
};
for (var row = 0; row < values.GetLength(0); row++)
{
var currentRow = row; // for closure
callbacks.Add(vals => vals.Populate(values, currentRow));
setupSequence.Returns(true);
}
setupSequence.Returns(false);
mock.Setup(reader => reader.GetValues(It.IsAny<object[]>())).CallbackSequence(callbacks.ToArray());
}
private static void Populate<T>(this IList<T> target, IList<T> source)
{
for (var i = 0; i < target.Count; i++)
{
target[i] = source[i];
}
}
private static void Populate<T>(this IList<T> target, T[,] sourceTable, int row)
{
for (var i = 0; i < sourceTable.GetLength(1); i++)
{
target[i] = sourceTable[row, i];
}
}
private static void CallbackSequence<T, TResult, TArg>(this ISetup<T, TResult> setup, params Action<TArg>[] callbacks) where T : class
{
var queue = new ConcurrentQueue<Action<TArg>>(callbacks);
setup.Callback((TArg arg) =>
{
Action<TArg> callback;
if (!queue.TryDequeue(out callback))
{
Assert.Fail("More callbacks were invoked than defined in sequence");
}
callback(arg);
});
}
}
用法:
const int ItemsCount = 1000;
var dataReaderMock = new Mock<IDataReader>();
var values = new object[ItemsCount, 2];
for (var i = 0; i < ItemsCount; i++)
{
values[i, 0] = i + 1;
values[i, 1] = (i + 1).ToString();
}
dataReaderMock.SetupDataReader(new List<string> {"Col1", "Col2"}, values);
我是 Moq 的新手,我正在努力编写单元测试来测试将 SqlDataAdapter
转换为 System.DataView
的方法。这是我的方法:
private DataView ResolveDataReader(IDataReader dataReader)
{
DataTable table = new DataTable();
for (int count = 0; count < dataReader.FieldCount; count++)
{
DataColumn col = new DataColumn(dataReader.GetName(count),
dataReader.GetFieldType(count));
table.Columns.Add(col);
}
while (dataReader.Read())
{
DataRow dr = table.NewRow();
for (int i = 0; i < dataReader.FieldCount; i++)
{
dr[i] = dataReader.GetValue(dataReader.GetOrdinal(dataReader.GetName(i)));
}
table.Rows.Add(dr);
}
return table.DefaultView;
}
我正在尝试创建类似的东西:
var dataReaderMock = new Mock<IDataReader>();
var records = new Mock<IDataRecord>();
dataReaderMock.Setup(x => x.FieldCount).Returns(2);
dataReaderMock.Setup(x => x.Read()).Returns(() => records);
我想传递一些数据并验证它是否已转换。
谢谢。
你的模拟在正确的轨道上,但是 dataReaderMock.Setup(x => x.Read()).Returns(() => records);
是你出错的地方,因为 .Read
returns 一个 bool,而不是记录本身,它被读出IDataReader
用你的方法。
安排模拟:
var dataReader = new Mock<IDataReader>();
dataReader.Setup(m => m.FieldCount).Returns(2); // the number of columns in the faked data
dataReader.Setup(m => m.GetName(0)).Returns("First"); // the first column name
dataReader.Setup(m => m.GetName(1)).Returns("Second"); // the second column name
dataReader.Setup(m => m.GetFieldType(0)).Returns(typeof(string)); // the data type of the first column
dataReader.Setup(m => m.GetFieldType(1)).Returns(typeof(string)); // the data type of the second column
你可以安排列来模拟更多的真实数据,类型等。在你的系统中,只要确保第一个计数,GetName
的数量s 和 GetFieldType
的数量是同步的。
要排列 .Read()
,我们可以使用 SetupSequence:
dataReader.SetupSequence(m => m.Read())
.Returns(true) // Read the first row
.Returns(true) // Read the second row
.Returns(false); // Done reading
要在测试中使用它 你可以将它提取到一个方法中:
private const string Column1 = "First";
private const string Column2 = "Second";
private const string ExpectedValue1 = "Value1";
private const string ExpectedValue2 = "Value1";
private static Mock<IDataReader> CreateDataReader()
{
var dataReader = new Mock<IDataReader>();
dataReader.Setup(m => m.FieldCount).Returns(2);
dataReader.Setup(m => m.GetName(0)).Returns(Column1);
dataReader.Setup(m => m.GetName(1)).Returns(Column2);
dataReader.Setup(m => m.GetFieldType(0)).Returns(typeof(string));
dataReader.Setup(m => m.GetFieldType(1)).Returns(typeof(string));
dataReader.Setup(m => m.GetOrdinal("First")).Returns(0);
dataReader.Setup(m => m.GetValue(0)).Returns(ExpectedValue1);
dataReader.Setup(m => m.GetValue(1)).Returns(ExpectedValue2);
dataReader.SetupSequence(m => m.Read())
.Returns(true)
.Returns(true)
.Returns(false);
return dataReader;
}
(或者,您可以将其安排在 Setup
上,如果这对您的测试更有意义 class - 在这种情况下,dataReader
模拟将是一个字段,而不是返回值)
示例测试。然后它可以像这样使用:
[Test]
public void ResovleDataReader_RowCount()
{
var dataReader = CreateDateReader();
var view = ResolveDataReader(dataReader.Object);
Assert.AreEqual(2, view.Count);
}
[Test]
public void ResolveDataReader_NamesColumn1()
{
var dataReader = CreateDataReader();
var view = ResolveDataReader(dataReader.Object);
Assert.AreEqual(Column1, view.Table.Columns[0].ColumnName);
}
[Test]
public void ResolveDataReader_PopulatesColumn1()
{
var dataReader = CreateDataReader();
var view = ResolveDataReader(dataReader.Object);
Assert.AreEqual(ExpectedValue1, view.Table.Rows[0][0]);
}
// Etc..
(我使用过 NUnit,但它与测试方法上的不同属性和不同的断言语法类似,适用于不同的测试框架)
顺便说一句,我通过将 ResolveDataReader
更改为 internal
并设置 InternalsVisibleTo
来使上述工作正常进行,但我假设您有进入此私有方法的途径,因为您您已经尽力测试它了。
我的 class 设置 IDataReader
模拟:
public static class DataReaderMock
{
public static void SetupDataReader(this Mock<IDataReader> mock, ICollection<string> columns, object[,] values)
{
if (columns.Count != values.GetLength(1))
{
throw new ArgumentException($"The number of named columns must be identical to the number of columns in the 2d values array: {columns.Count} compared to {values.GetLength(1)}");
}
mock.Setup(reader => reader.FieldCount).Returns(columns.Count);
var setupSequence = mock.SetupSequence(reader => reader.Read());
var callbacks = new List<Action<object[]>>
{
vals => vals.Populate(columns.Cast<object>().ToList())
};
for (var row = 0; row < values.GetLength(0); row++)
{
var currentRow = row; // for closure
callbacks.Add(vals => vals.Populate(values, currentRow));
setupSequence.Returns(true);
}
setupSequence.Returns(false);
mock.Setup(reader => reader.GetValues(It.IsAny<object[]>())).CallbackSequence(callbacks.ToArray());
}
private static void Populate<T>(this IList<T> target, IList<T> source)
{
for (var i = 0; i < target.Count; i++)
{
target[i] = source[i];
}
}
private static void Populate<T>(this IList<T> target, T[,] sourceTable, int row)
{
for (var i = 0; i < sourceTable.GetLength(1); i++)
{
target[i] = sourceTable[row, i];
}
}
private static void CallbackSequence<T, TResult, TArg>(this ISetup<T, TResult> setup, params Action<TArg>[] callbacks) where T : class
{
var queue = new ConcurrentQueue<Action<TArg>>(callbacks);
setup.Callback((TArg arg) =>
{
Action<TArg> callback;
if (!queue.TryDequeue(out callback))
{
Assert.Fail("More callbacks were invoked than defined in sequence");
}
callback(arg);
});
}
}
用法:
const int ItemsCount = 1000;
var dataReaderMock = new Mock<IDataReader>();
var values = new object[ItemsCount, 2];
for (var i = 0; i < ItemsCount; i++)
{
values[i, 0] = i + 1;
values[i, 1] = (i + 1).ToString();
}
dataReaderMock.SetupDataReader(new List<string> {"Col1", "Col2"}, values);