Перемешивание SqlConnection, SqlCommand и SqlReader в C # с использованием MsTest - PullRequest
1 голос
/ 14 октября 2019

Я наткнулся на этот ответ , и я заинтересован в реализации второго ответа с использованием 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; }
}

UnitTest

[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))

1 Ответ

1 голос
/ 14 октября 2019

Тестируемый целевой метод должен подвергаться рефакторингу, чтобы зависеть от абстракций, а не от проблем реализации.

Например,

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"));

Такой же подход будет использоваться в производственной среде для соединения с сервером.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...