最小起订量和 SqlConnection?
Moq and SqlConnection?
我正在为我们的一个产品编写单元测试,并已使用 Moq 成功模拟到 Entity Framework 的连接。但是,我遇到了以下方法:
public static productValue findValues(string productName, string dbConnectionString)
{
try
{
SqlConnection conn = new SqlConnection(dbConnectionString);
conn.Open();
//Do stuff
}
}
它使用传递的连接字符串在该方法中访问我们的数据库。是否可以使用 Moq 设置模拟数据库并创建指向模拟数据库的连接字符串?我正在尝试按照
的方式做一些事情
var mockSqlConnnection = new Mock<SqlConnection>();
虽然我不确定这是否是正确的方法,因为这会模拟连接本身而不是数据库。
看看 Repository Pattern,本质上你会在你的消费 classes 中模拟数据,而不是担心与数据库对话的实现。
基本上,您将拥有一个存储库
namespace ContosoUniversity.DAL
{
public class StudentRepository : IStudentRepository, IDisposable
{
private SchoolContext context;
public StudentRepository(SchoolContext context)
{
this.context = context;
}
public IEnumerable<Student> GetStudents()
{
return context.Students.ToList();
}
// ... more
然后在其他 classes:
中消耗
public class StudentController : Controller
{
private IStudentRepository studentRepository;
public StudentController(IStudentRepository studentRepository)
{
this.studentRepository = studentRepository;
}
并用作:
public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
{
var students = from s in studentRepository.GetStudents()
select s;
完整示例在顶部的 link 中。
那么你将把一个模拟的存储库传递到你的 class:
// arrange
var mockedRepo = new Mock<IStudentRepository>();
// configure
// act
var controller = new StudentController(mockedRepo.Object);
// do stuff
// assert
我遇到了类似的问题。
我在继承自 ISqlDataContext
接口的 SqlConnection 周围引入了一个 SqlDataContext
包装器:
class SqlDataContext : ISqlDataContext {
private readonly SqlConnection _connection;
public SqlDataContext(string connectionString)
{
_connection = CreateConnection(connectionString);
}
public IDataReader ExecuteReader(string storedProcedureName, ICollection<SqlParameter> parameters)
{
// execute the command here using the _connection private field.
// This is where your conn.Open() and "do stuff" happens.
}
private SqlConnection CreateConnection(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentNullException("connectionString");
}
return new SqlConnection(connectionString);
}
}
interface ISqlDataContext
{
IDataReader ExecuteReader(string storedProcedureName, ICollection<SqlParameter> parameters);
}
您可以根据需要向 ISqlDataContext 添加重载。
这意味着您可以根据需要使用 Moq 或类似的和 return 模拟值来模拟 ISqlDataContext。
意味着您随后可以测试您的存储库或通过 SqlConnection 访问数据库的任何其他内容,而无需实际访问数据库。
另一个优点是您可以根据需要使用 DI / IoC 注入 ISqlContext。
晚了但为什么不使用 mstest:
[TestMethod]
MyTestWithInternSqlConnection()
{
using (ShimsContext.Create())
{
// simulate a connection
ShimSqlConnection.AllInstances.Open = connection => { };
string commandText;
// shim-Mock all called methods
ShimSqlCommand.AllInstances.ExecuteReader = command =>
{
commandText = command.CommandText;
return new ShimSqlDataReader();
};
int readCount = 0;
ShimSqlDataReader.AllInstances.Read = reader => readCount == 0;
ShimSqlDataReader.AllInstances.GetSqlStringInt32 = (reader, i) =>
{
readCount++;
return "testServer";
};
var theReadedString = AMethodUnderTestThatReadsFromDatabaseAString();
Assert.IsTrue(theReadedString == "testServer");
}
}
您需要添加对 System.Data 的引用,然后为其添加一个 Fake。
https://msdn.microsoft.com/en-us/library/hh549175.aspx
更好的是,如果您更改实现并且可以更改使用的读取层但是 ...
如果您的主项目上的连接字符串变量正在调用配置管理器,您只需使用主项目上的设置在单元测试上设置配置管理器。之后,参考System.Configuration
,无需创建任何配置文件。
using System.Configuration;
using Xunit;
namespace xUnitExample
{
public class ExampleTests
{
[Fact]
public void TesReturnSomething()
{
string value = "connection";
ConfigurationManager.AppSettings["key"] = value;
//Your Test
}
}
}
如何对 SqlConnection 进行 Moq:在具有 return 类型 IDbConnection 的受保护虚拟方法中包装 SqlConnection,以便可以从其模拟中设置它parent class.
我们正在做与接受的答案类似的事情,但只针对最小起订量(和 Moq.Protected)。
在您的数据存储库中 class break-out 您连接到它自己的功能。在此示例中,它称为 GetConnection。使它成为受保护的虚拟方法,以便我们稍后可以模拟它。并添加一个 return 类型的 IDbConnection。这是关键。
public Stuff FindValueMethod(string product)
{
...
try
{
using (var connection = GetConnection())
{
var result = await FindValue(connection, params);
//Do stuff
}
return stuff;
}
...
protected virtual IDbConnection GetConnection()
{
return new SqlConnection(injectedSettings.connectionString)
}
稍后,在数据存储库单元测试中,像往常一样执行依赖项注入,但使用“Mock<>”包装您的依赖项。
为了模拟受保护的虚拟 classes,我们需要它们可以从主 DataRepository class 扩展。所以我们还必须模拟数据回购。一定要添加一个模拟数据库连接。
using Moq;
using Moq.Protected;
...
private readonly Mock<DbConnection> _connection;
private readonly Mock<ILogger<DataRepository>> _logger;
private readonly Mock<Param> _param;
private readonly Mock<DataRepository> _dataRepository;
...
DataRepositoryTestsConstructor()
{
_connection = new Mock<DbConnection>();
_logger = new Mock<ILogger<DataRepository>>();
_param = new Mock<Param>();
//use the Mock.Object for dependency injections into the Mock repo.
_dataRepository = new Mock<DataRepository>(_logger.Object, _param.Object);
}
...
[Fact]
public async Task FindValueMethod_Returns_ProductPrice()
{
//Arrange
_dataRepository.Protected().Setup<IDbConnection>("GetConnection").Returns(_connection.Object);
//Act
var result = await _dataRepository.Object.FindValueMethod("rhubarb");
//Assert
Assert.NotNull(result);
}
在上面的单元测试中,一旦我们有了一个模拟数据存储库,那么我们就可以设置一个 return 类型 IDbConnection 的受保护方法。并且 return 模拟连接 object。现在,调试器将像黄油一样滑过 using (var connection = GetConnection())
行。
模拟回购协议和回购协议依赖项有点工作。并重构您的代码以获得受保护的虚拟数据库连接方法。但是编配部分的 one-liner 是值得的。
我正在为我们的一个产品编写单元测试,并已使用 Moq 成功模拟到 Entity Framework 的连接。但是,我遇到了以下方法:
public static productValue findValues(string productName, string dbConnectionString)
{
try
{
SqlConnection conn = new SqlConnection(dbConnectionString);
conn.Open();
//Do stuff
}
}
它使用传递的连接字符串在该方法中访问我们的数据库。是否可以使用 Moq 设置模拟数据库并创建指向模拟数据库的连接字符串?我正在尝试按照
的方式做一些事情var mockSqlConnnection = new Mock<SqlConnection>();
虽然我不确定这是否是正确的方法,因为这会模拟连接本身而不是数据库。
看看 Repository Pattern,本质上你会在你的消费 classes 中模拟数据,而不是担心与数据库对话的实现。
基本上,您将拥有一个存储库
namespace ContosoUniversity.DAL
{
public class StudentRepository : IStudentRepository, IDisposable
{
private SchoolContext context;
public StudentRepository(SchoolContext context)
{
this.context = context;
}
public IEnumerable<Student> GetStudents()
{
return context.Students.ToList();
}
// ... more
然后在其他 classes:
中消耗 public class StudentController : Controller
{
private IStudentRepository studentRepository;
public StudentController(IStudentRepository studentRepository)
{
this.studentRepository = studentRepository;
}
并用作:
public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
{
var students = from s in studentRepository.GetStudents()
select s;
完整示例在顶部的 link 中。
那么你将把一个模拟的存储库传递到你的 class:
// arrange
var mockedRepo = new Mock<IStudentRepository>();
// configure
// act
var controller = new StudentController(mockedRepo.Object);
// do stuff
// assert
我遇到了类似的问题。
我在继承自 ISqlDataContext
接口的 SqlConnection 周围引入了一个 SqlDataContext
包装器:
class SqlDataContext : ISqlDataContext {
private readonly SqlConnection _connection;
public SqlDataContext(string connectionString)
{
_connection = CreateConnection(connectionString);
}
public IDataReader ExecuteReader(string storedProcedureName, ICollection<SqlParameter> parameters)
{
// execute the command here using the _connection private field.
// This is where your conn.Open() and "do stuff" happens.
}
private SqlConnection CreateConnection(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentNullException("connectionString");
}
return new SqlConnection(connectionString);
}
}
interface ISqlDataContext
{
IDataReader ExecuteReader(string storedProcedureName, ICollection<SqlParameter> parameters);
}
您可以根据需要向 ISqlDataContext 添加重载。
这意味着您可以根据需要使用 Moq 或类似的和 return 模拟值来模拟 ISqlDataContext。
意味着您随后可以测试您的存储库或通过 SqlConnection 访问数据库的任何其他内容,而无需实际访问数据库。
另一个优点是您可以根据需要使用 DI / IoC 注入 ISqlContext。
晚了但为什么不使用 mstest:
[TestMethod]
MyTestWithInternSqlConnection()
{
using (ShimsContext.Create())
{
// simulate a connection
ShimSqlConnection.AllInstances.Open = connection => { };
string commandText;
// shim-Mock all called methods
ShimSqlCommand.AllInstances.ExecuteReader = command =>
{
commandText = command.CommandText;
return new ShimSqlDataReader();
};
int readCount = 0;
ShimSqlDataReader.AllInstances.Read = reader => readCount == 0;
ShimSqlDataReader.AllInstances.GetSqlStringInt32 = (reader, i) =>
{
readCount++;
return "testServer";
};
var theReadedString = AMethodUnderTestThatReadsFromDatabaseAString();
Assert.IsTrue(theReadedString == "testServer");
}
}
您需要添加对 System.Data 的引用,然后为其添加一个 Fake。
https://msdn.microsoft.com/en-us/library/hh549175.aspx 更好的是,如果您更改实现并且可以更改使用的读取层但是 ...
如果您的主项目上的连接字符串变量正在调用配置管理器,您只需使用主项目上的设置在单元测试上设置配置管理器。之后,参考System.Configuration
,无需创建任何配置文件。
using System.Configuration;
using Xunit;
namespace xUnitExample
{
public class ExampleTests
{
[Fact]
public void TesReturnSomething()
{
string value = "connection";
ConfigurationManager.AppSettings["key"] = value;
//Your Test
}
}
}
如何对 SqlConnection 进行 Moq:在具有 return 类型 IDbConnection 的受保护虚拟方法中包装 SqlConnection,以便可以从其模拟中设置它parent class.
我们正在做与接受的答案类似的事情,但只针对最小起订量(和 Moq.Protected)。
在您的数据存储库中 class break-out 您连接到它自己的功能。在此示例中,它称为 GetConnection。使它成为受保护的虚拟方法,以便我们稍后可以模拟它。并添加一个 return 类型的 IDbConnection。这是关键。
public Stuff FindValueMethod(string product)
{
...
try
{
using (var connection = GetConnection())
{
var result = await FindValue(connection, params);
//Do stuff
}
return stuff;
}
...
protected virtual IDbConnection GetConnection()
{
return new SqlConnection(injectedSettings.connectionString)
}
稍后,在数据存储库单元测试中,像往常一样执行依赖项注入,但使用“Mock<>”包装您的依赖项。 为了模拟受保护的虚拟 classes,我们需要它们可以从主 DataRepository class 扩展。所以我们还必须模拟数据回购。一定要添加一个模拟数据库连接。
using Moq;
using Moq.Protected;
...
private readonly Mock<DbConnection> _connection;
private readonly Mock<ILogger<DataRepository>> _logger;
private readonly Mock<Param> _param;
private readonly Mock<DataRepository> _dataRepository;
...
DataRepositoryTestsConstructor()
{
_connection = new Mock<DbConnection>();
_logger = new Mock<ILogger<DataRepository>>();
_param = new Mock<Param>();
//use the Mock.Object for dependency injections into the Mock repo.
_dataRepository = new Mock<DataRepository>(_logger.Object, _param.Object);
}
...
[Fact]
public async Task FindValueMethod_Returns_ProductPrice()
{
//Arrange
_dataRepository.Protected().Setup<IDbConnection>("GetConnection").Returns(_connection.Object);
//Act
var result = await _dataRepository.Object.FindValueMethod("rhubarb");
//Assert
Assert.NotNull(result);
}
在上面的单元测试中,一旦我们有了一个模拟数据存储库,那么我们就可以设置一个 return 类型 IDbConnection 的受保护方法。并且 return 模拟连接 object。现在,调试器将像黄油一样滑过 using (var connection = GetConnection())
行。
模拟回购协议和回购协议依赖项有点工作。并重构您的代码以获得受保护的虚拟数据库连接方法。但是编配部分的 one-liner 是值得的。