模拟测试 SqlDataReader 结果
Mock Testing the SqlDataReader results
我有一个与员工一起工作的应用程序。所以我决定使用 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 方法...请给出从哪里开始测试此类方法的直接访问的建议到数据库。
与实现的紧密耦合 details/concerns 使得这很难单独进行单元测试。
重构以依赖于可以模拟的抽象。
例如
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
}
我有一个与员工一起工作的应用程序。所以我决定使用 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 方法...请给出从哪里开始测试此类方法的直接访问的建议到数据库。
与实现的紧密耦合 details/concerns 使得这很难单独进行单元测试。
重构以依赖于可以模拟的抽象。
例如
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
}