如何在Visual Studio Test中模拟数据存储库?

时间:2018-02-23 00:34:34

标签: c# unit-testing moq mstest

我是单元测试的新手。我有这些类 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扔了   exception:System.InvalidOperationException:ConnectionString   财产尚未初始化。

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

我是否错误地嘲笑?

2 个答案:

答案 0 :(得分: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
        //...
    }
}

答案 1 :(得分:0)

在您的单元测试中,您创建了一个模拟数据源但不使用它;这就是调用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);