最小起订量和 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 是值得的。