使用 MsTest 在 C# 中模拟 SqlConnection、SqlCommand 和 SqlReader



我遇到了这个答案,我有兴趣使用 Fake 实现第二个答案。这是另一个。

我并没有真正理解那里的所有概念,我仍在阅读和理解文档,有人可以帮助使用我的代码,我正在尝试访问有关如何在此处使用 Fake/Shim/Stub/Mock 的客户列表?

您也可以重写FindAll方法,以防万一要重构它以接受依赖项。

讨论后编辑

public class Data
{
private Func<IDbConnection> Factory { get; }
public Data(Func<IDbConnection> factory)
{
Factory = factory;
}
public IList<Customer> FindAll()
{
using (var connection = Factory.Invoke())
{
const string sql = "SELECT Id, Name FROM Customer";
using (var command = new SqlCommand(sql, (SqlConnection) connection))
{
command.Connection.Open();
using (var reader = command.ExecuteReader())
{
IList<Customer> rows = new List<Customer>();
while (reader.Read())
{
rows.Add(new Customer
{
Id = reader.GetInt32(reader.GetOrdinal("Id")),
Name = reader.GetString(reader.GetOrdinal("Name"))
});
}
return rows;
}
}
}
}
}

客户

public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}

单元测试

[TestMethod]
public void TestDB()
{
var commandMock = new Mock<IDbCommand>();
var readerMock = new Mock<IDataReader>();
commandMock.Setup(m => m.ExecuteReader()).Returns(readerMock.Object).Verifiable();
var parameterMock = new Mock<IDbDataParameter>();
commandMock.Setup(m => m.CreateParameter()).Returns(parameterMock.Object);
commandMock.Setup(m => m.Parameters.Add(It.IsAny<IDbDataParameter>())).Verifiable();
var connectionMock = new Mock<IDbConnection>();
connectionMock.Setup(m => m.CreateCommand()).Returns(commandMock.Object);
var data = new Data(() => connectionMock.Object);
var result = data.FindAll();
Console.WriteLine(result);
}

错误

有一个依赖关系打嗝,System.Data.SqlClient添加,另一个错误随之而来。

System.InvalidCastException: 无法强制转换类型的对象 "Castle.Proxies.IDbConnectionProxy"键入 'System.Data.SqlClient.SqlConnection'.

指向此行

using (var command = new SqlCommand(sql, (SqlConnection) connection))

应该重构所测试的目标方法,以依赖于抽象而不是实现问题。

例如

public class Data {
private Func<IDbConnection> Factory { get; }
public Data(Func<IDbConnection> factory) {
Factory = factory;
}
public IList<Customer> FindAll() {
using (IDbConnection connection = Factory.Invoke()) {
const string sql = "SELECT Id, Name FROM Customer";
using (IDbCommand command = connection.CreateCommand()) {                    
command.CommandText = sql;
connection.Open();
using (IDataReader reader = command.ExecuteReader()) {
IList<Customer> rows = new List<Customer>();
while (reader.Read()) {
rows.Add(new Customer {
Id = reader.GetInt32(reader.GetOrdinal("Id")),
Name = reader.GetString(reader.GetOrdinal("Name"))
});
}
return rows;
}
}
}
}
}

从那里可以模拟抽象,使其在单元测试隔离时按预期运行。

[TestClass]
public class DataTests{
[TestMethod]
public void Should_Return_Customer() {
//Arrange
var readerMock = new Mock<IDataReader>();
readerMock.SetupSequence(_ => _.Read())
.Returns(true)
.Returns(false);
readerMock.Setup(reader => reader.GetOrdinal("Id")).Returns(0);
readerMock.Setup(reader => reader.GetOrdinal("Name")).Returns(1);
readerMock.Setup(reader => reader.GetInt32(It.IsAny<int>())).Returns(1);
readerMock.Setup(reader => reader.GetString(It.IsAny<int>())).Returns("Hello World");
var commandMock = new Mock<IDbCommand>();            
commandMock.Setup(m => m.ExecuteReader()).Returns(readerMock.Object).Verifiable();
var connectionMock = new Mock<IDbConnection>();
connectionMock.Setup(m => m.CreateCommand()).Returns(commandMock.Object);
var data = new Data(() => connectionMock.Object);
//Act
var result = data.FindAll();
//Assert - FluentAssertions
result.Should().HaveCount(1);
commandMock.Verify(); //since it was marked verifiable.
}
}

对于集成测试,可以使用与数据库的实际连接来验证功能

var data = new Data(() => new SqlConnection("live connection string here"));

在生产中将使用相同的方法来连接到服务器。

这样做的问题是IDbCommand没有方法ExecuteNonQueryAsync或任何其他Async方法。

为什么我应该删除异步功能只是为了测试