【问题标题】:How to mock data repository in Visual Studio Test?如何在 Visual Studio 测试中模拟数据存储库?
【发布时间】:2018-02-23 00:34:33
【问题描述】:

我是单元测试的新手。我有这些类 AccountBl 调用 DataStore 使用 SqlConnection 从数据库中获取数据。

我需要通过模拟数据源来测试AccountBl.GetInvestmentAccounts方法,即使没有数据库连接也需要运行测试。

这里是给定的类AccountBl

public class AccountBl
{
    private readonly DataStore dataStore = new DataStore();

    public List<Account> GetInvestmentAccounts(int clientId, AccountType accountType)
    {
        if (accountType == AccountType.Investment)
        {
            var accounts = dataStore.LoadAccounts(clientId);
            return accounts.Where(a => a.AccountType == AccountType.Investment).ToList();
        }
        throw new Exception("Invalid account type provided");
    }
}

DataStore

public class DataStore
{
    public static string GetAccountsSql = "irrelevant query";

    public virtual List<Account> LoadAccounts(int clientId)
    {
        using (var connection = CreateConnection())
        {
            var sqlCommand = connection.CreateCommand();
            sqlCommand.CommandText = GetAccountsSql;
            sqlCommand.CommandType = CommandType.Text;
            sqlCommand.Parameters.Add("@clientId", clientId);

            var reader = sqlCommand.ExecuteReader();
            var accounts = new List<Account>();
            while (reader.Read())
            {
                var account = new Account();
                account.AccountNumber = (string)reader["number"];
                account.AccountOwner = clientId;
                if (reader["accountType"] == null || reader["accountType"] == DBNull.Value)
                {
                    account.AccountType = AccountType.Checking;
                }
                else
                {
                    account.AccountType =
                        (AccountType)Enum.Parse(typeof(AccountType), reader["accountType"].ToString());
                }
                accounts.Add(account);
            }
            return accounts;
        }

    }

    private static SqlConnection CreateConnection()
    {
        var sqlConnection = new SqlConnection(ConfigurationManager.AppSettings["ConnectionString"]);
        sqlConnection.Open();
        return sqlConnection;
    }
}

这是我的TestClass

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void GetInvestmentAccountsTest()
    {
        var clientId = 25;

        var mockAccounts = new List<Account>
        {
            new Account{AccountNumber = "aaa", AccountOwner = clientId, AccountType = AccountType.Investment},
            new Account{AccountNumber = "bbb", AccountOwner = clientId, AccountType = AccountType.Savings},
            new Account{AccountNumber = "ccc", AccountOwner = clientId, AccountType = AccountType.Checking},
        };

        var mockDatastore = new Mock<DataStore>();
        mockDatastore.Setup(x => x.LoadAccounts(clientId)).Returns(mockAccounts);

        var accountBl = new AccountBl();

        var accounts = accountBl.GetInvestmentAccounts(clientId, AccountType.Investment);


    }
}

我运行时收到错误消息

消息:测试方法 ScreeningSample.Tests.UnitTest1.GetInvestmentAccountsTest 抛出 异常:System.InvalidOperationException:ConnectionString 属性尚未初始化。

显然它试图创建一个连接,但我需要在没有连接的情况下运行测试。

我是不是在模拟错误?

【问题讨论】:

    标签: c# unit-testing moq mstest


    【解决方案1】:

    被测主题中的readonly DataStore dataStore 与类紧密耦合,因此很难单独测试该主题。您需要能够在测试期间替换该依赖项,以便能够单独进行测试。

    考虑首先抽象数据存储,

    public interface IDataStore {
        List<Account> LoadAccounts(int clientId);
    }
    

    并且通过构造函数注入让主题显式依赖于该抽象,因为类应该依赖于抽象而不是具体化。

    public class AccountBl {
        private readonly IDataStore dataStore;
    
        public AccountBl(IDataStore dataStore) {
            this.dataStore = dataStore;
        }
    
        public List<Account> GetInvestmentAccounts(int clientId, AccountType accountType) {
            if (accountType == AccountType.Investment) {
                var accounts = dataStore.LoadAccounts(clientId);
                return accounts.Where(a => a.AccountType == AccountType.Investment).ToList();
            }
            throw new Exception("Invalid account type provided");
        }
    }
    

    SqlConnection 是一个实现细节,AccountBl 不再关心

    DataStore 实现将派生自抽象。

    public class DataStore : IDataStore {
    
        public List<Account> LoadAccounts(int clientId) {
            //...code removed for brevity
        }
    
        //...
    }
    

    现在代码已经解耦,可以更加灵活地进行独立测试

    [TestClass]
    public class UnitTest1 {
        [TestMethod]
        public void GetInvestmentAccountsTest() {
            //Arrange
            var clientId = 25;
            var mockAccounts = new List<Account> {
                new Account{AccountNumber = "aaa", AccountOwner = clientId, AccountType = AccountType.Investment},
                new Account{AccountNumber = "bbb", AccountOwner = clientId, AccountType = AccountType.Savings},
                new Account{AccountNumber = "ccc", AccountOwner = clientId, AccountType = AccountType.Checking},
            };
    
            var mockDatastore = new Mock<IDataStore>();
            mockDatastore.Setup(_ => _.LoadAccounts(clientId)).Returns(mockAccounts);
    
            var subject = new AccountBl(mockDatastore.Object);
    
            //Act
            var accounts = subject.GetInvestmentAccounts(clientId, AccountType.Investment);
    
            //Assert
            //...
        }
    }
    

    【讨论】:

      【解决方案2】:

      在您的单元测试中,您创建了一个模拟数据源但不使用它;这就是为什么DataStore::LoadAcounts 被调用的原因。您应该在构造函数中注入DataStore 的实例,而不是在AccountBl 类中创建DataStore 的实例。这是依赖注入的一种形式。

      public class AccountBl
      {
          private DataStore _dataStore;
      
          public AccountBl(DataStore dataStore)
          {
              _dataStore = dataStore;
          }
      
          public List<Account> GetInvestmentAccounts(int clientId, AccountType accountType)
          {
              if (accountType == AccountType.Investment)
              {
                  var accounts = _dataStore.LoadAccounts(clientId);
                  return accounts.Where(a => a.AccountType == AccountType.Investment).ToList();
              }
              throw new Exception("Invalid account type provided");
          }
      }
      

      现在在单元测试中注入模拟数据源:

      var mockDatastore = new Mock<DataStore>();
      mockDatastore.Setup(x => x.LoadAccounts(clientId)).Returns(mockAccounts);
      
      // Inject mock data source
      var accountBl = new AccountBl(mockDataStore.Object);
      
      var accounts = accountBl.GetInvestmentAccounts(clientId, AccountType.Investment); 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-05
        • 2020-10-10
        • 1970-01-01
        • 2018-12-17
        • 1970-01-01
        • 2021-09-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多