.net - 模拟测试 SqlDataReader 结果
问题描述
我有一个与员工合作的应用程序。所以我决定使用 Mock 来测试 Employee Repository。
这是我的测试方法
class EmployeesRepository : IEmployeeRepository ...
public IEnumerable<Employee> GetAllEmployees()
{
List<Employee> list = new List<Employee>();
try
{
string connectionString = _secureConfig.Value.MyDbSetting;
string sql = "select id, firstname, lastname, entrydate, email from empoyees";
using SqlConnection connection = new SqlConnection(connectionString);
using SqlCommand command = new SqlCommand(sql, connection);
connection.Open();
using SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
list.Add(new Employee
{
Id = reader.GetString(0),
FirstName = reader.GetString(1),
LastName = reader.GetString(2),
EntryDate = reader.GetDateTime(3),
Email = (reader.IsDBNull(4) ? null : reader.GetString(4))
});
}
}
catch (Exception ex)
{
_log.LogError(ex, "An error is caught when getting all employees");
}
return list;
}
我的问题是在这种情况下模拟什么以及如何模拟......我应该模拟数据读取器,还是只模拟 ExecutReader 方法......请提供建议,从哪里开始测试这些方法并直接访问数据库。
解决方案
与实现细节/问题的紧密耦合使得这很难单独进行单元测试。
重构依赖于可以模拟的抽象。
例如
class EmployeesRepository : IEmployeeRepository {
private readonly IDbConnectionFactory dbConnectionFactory;
//...
public EmployeesRepository(IDbConnectionFactory dbConnectionFactory /*,...*/) {
this.dbConnectionFactory = dbConnectionFactory;
//...
}
public IEnumerable<Employee> GetAllEmployees() {
List<Employee> list = new List<Employee>();
try {
string connectionString = _secureConfig.Value.MyDbSetting;
string sql = "select id, firstname, lastname, entrydate, email from empoyees";
using (IDbConnection connection = dbConnectionFactory.CreateConnection(connectionString)) {
using (IDbCommand command = connection.CreateCommand()) {
command.CommandText = sql;
connection.Open();
using (IDataReader reader = command.ExecuteReader()) {
while (reader.Read()) {
list.Add(new Employee {
Id = reader.GetString(0),
FirstName = reader.GetString(1),
LastName = reader.GetString(2),
EntryDate = reader.GetDateTime(3),
Email = (reader.IsDBNull(4) ? null : reader.GetString(4))
});
}
}
}
}
} catch (Exception ex) {
_log.LogError(ex, "An error is caught when getting all employees");
}
return list;
}
}
其中IDbConnectionFactory
定义为
public interface IDbConnectionFactory {
///<summary>
/// Creates a connection based on the given database name or connection string.
///</summary>
IDbConnection CreateConnection(string nameOrConnectionString);
}
将在您的 DI 容器中注册的运行时实现如下所示
class SqlConnectionFactory : IDbConnectionFactory {
public IDbConnection CreateConnection(string nameOrConnectionString) {
return new SqlConnection(nameOrConnectionString);
}
}
//...
services.AddSingleton<IDbConnectionFactory, SqlConnectionFactory>();
//...
为了测试上述内容,可以根据需要模拟抽象
public void ShouldGetAllEmployees() {
//Arrange
var readerMock = new Mock<IDataReader>();
//...setup reader members as needed
var commandMock = new Mock<IDbCommand>();
commandMock.Setup(m => m.ExecuteReader())
.Returns(readerMock.Object);
var connectionMock = new Mock<IDbConnection>();
connectionMock.Setup(m => m.CreateCommand())
.Returns(commandMock.Object);
//..Setup...
var connectionFactoryMock = new Mock<IDbConnectionFactory>();
connectionFactoryMock
.Setup(m => m.CreateConnection(It.IsAny<string>()))
.Returns(connectionMock.Object);
EmployeesRepository sut = new EmployeesRepository(connectionFactoryMock.Object);
//Act
IEnumerable<Employee> actual = sut.GetAllEmployees();
//Assert
//...assert desired behavior
}
推荐阅读
- ios - 以编程方式在垂直堆栈视图中添加多个水平堆栈视图
- floating-point - IEEE-754 复合函数范围限制的基本原理
- r - 将日期格式从数据框转换为时间序列的问题
- python - calendar.monthrange() 函数显示错误
- xml - 当我在布局上放置背景时,我的应用程序无法运行并出现错误
- php - 通过两个自定义字段过滤自定义帖子类型
- javascript - 如何找出节点进程中的内存泄漏
- javascript - Vuejs关于选择更改组件属性
- javascript - Javascript:无法识别对象属性
- php - 无法从 MySQL 数据库中删除条目 - PHP