Можно ли макетировать параметр транзакции базы данных? - PullRequest
3 голосов
/ 29 июля 2009

Я пытаюсь выполнить модульное тестирование своей реализации интерфейса, и у меня возникли небольшие трудности с успешным макетом параметра SqlTransaction для одного из методов интерфейса.

Вот как выглядят интересующий меня интерфейс и тестируемый метод ..

public class MyInterface 
{ 
  void MyMethod(SqlTransaction SharedTransaction, DateTime EventTime);
} 

public class MyImplementation : MyInterface
{
  public void MyMethod(SqlTransaction SharedTransaction, DateTime EventTime)
  {
    DateTime dtLastEventTime = DateTime.MinValue;
    using(SqlCommand comm = SharedTransaction.Connection.CreateCommand())
    {
      comm.CommandText = SQL_GET_LAST_EVENTTIME_DEFINED_ELSEWHERE;
      comm.Parameters.AddWithValue("ParamName", 123);
      object oResult = comm.ExecuteScalar();
      dtLastEventTime = DateTime.Parse(oResult.ToString());
    }
    //Do something with dtLastEventTime
  }
}

Я использовал Moq и различные синтаксические подходы для макетирования объектов базы данных, и мне не повезло ... (Мне пришлось сделать какое-то преобразование в объекты System.Data.Common, чтобы получить немного далее .. DbTransaction, DbConnection, DbCommand и т. д.).

Что я хотел бы знать, так это в первую очередь, можно ли таким образом смоделировать транзакцию или я лаю здесь не то дерево. К счастью, я могу преобразовать интерфейс для использования общего параметра DbTransaction, а не SqlTransaction для конкретного провайдера, но я не уверен, что мне так трудно с этим издеваться.

Вот (и это может быть совершенно неверно, поэтому, пожалуйста, исправьте меня или прокомментируйте, если я не совсем правильно подхожу к этому), что у меня так далеко за насмешливый код ...

   var mockParams = new Mock<DbParameterCollection>();
    mockParams.Setup(p => p.Add(new SqlParameter("ParamName", 123)));
    var mockCommand = new Mock<DbCommand>();
    mockCommand.Setup(p => p.Parameters).Returns(mockParams.Object);
    var mockConnection = new Mock<DbConnection>();
    mockConnection.Setup(con => con.CreateCommand()).Returns(mockCommand.Object);
    var mockTrans = new Mock<DbTransaction>();
    mockTrans.Setup(t => t.Connection).Returns(mockConnection.Object);   

Однако, похоже, это вызывает исключение ArgumentException в строке mockCommand.Setup. (Неверная настройка для не перезаписываемого элемента)

Есть ли у кого-нибудь какие-либо идеи или предложения о том, как я мог бы правильно выполнить модульное тестирование этого метода с поддельным параметром SqlTransaction?

1 Ответ

3 голосов
/ 30 июля 2009

Питер, я собираюсь принять SqlServer бэкэнд.

Прежде всего, я бы реорганизовал MyMethod (), изменив тип входящего параметра на IDbTransaction в MyMethod (), чтобы вы могли передать свой макет. Затем я бы абстрагировал вызов ExecuteScalar (), чтобы можно было легко проверить параметры SqlCommand в моем тесте.

Поскольку Moq поддерживает рекурсивное моделирование, в тесте я бы смоделировал вызов sharedTransaction следующим образом:

var mockTrx = new Mock<IDbTransaction>();
mockTrx.Setup(txn => txn.Connection.CreateCommand()).Returns(new SqlCommand());

Создайте еще один макет для проверки параметров команды в вашем методе и возврата значения в вызове ExecuteScalar (), и все готово!

Рефакторинг MyMethod ():

public void MyMethod(IDbTransaction sharedTransaction, DateTime eventTime)
{
    DateTime lastEventTime;
    using (var cmd = (SqlCommand)sharedTransaction.Connection.CreateCommand())
    {
        cmd.CommandText = "somecmdtext";
        cmd.Parameters.AddWithValue("ParamName", 123);

        object oResult = dbUtility.ExecuteScalar(cmd);

        lastEventTime = DateTime.Parse(oResult.ToString());
    }

    //Do something with dtLastEventTime
    lastEventTime += TimeSpan.FromMinutes(1);
}

Тест:

[Test]
public void ShouldCallCorrectProcWithParams()
{
    var mockTrx = new Mock<IDbTransaction>();
    mockTrx.Setup(txn => txn.Connection.CreateCommand()).Returns(new SqlCommand());

    var dbUtil = new Mock<IDbUtility>();
    dbUtil.Setup(exec => exec.ExecuteScalar(
                             It.Is<SqlCommand>(
                             cmd => cmd.CommandText == "somecmdtext"
                             && cmd.Parameters.Count == 1
                             && cmd.Parameters[0].ParameterName == "ParamName")))
                 .Returns(DateTime.Now);

     var session = new Session {dbUtility = dbUtil.Object};

     session.MyMethod(mockTrx.Object, DateTime.Now);

     mockTrx.VerifyAll();
     dbUtil.VerifyAll();

}

Лично мне нравится тестировать параметры в моих вызовах SqlCommand, поэтому в этом примере я атаковал вашу проблему с этой точки зрения.

...