2009-07-29 25 views
3

Ich versuche Unit-Test meine Implementierung einer Schnittstelle zu testen, und ich habe ein wenig Schwierigkeiten, erfolgreich SqlTransaction-Parameter zu einer der Schnittstellenmethoden zu verspotten.Ist es möglich, einen Datenbanktransaktionsparameter zu verfälschen?

Hier ist, was die Schnittstelle und Methode im Test, die ich in aussehen ..

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 
    } 
} 

Ich habe mit Moq und verschiedene Syntax Ansätzen interessiert bin die Datenbank-Objekte verspotten und nicht viel mit Glück .. (Ich musste etwas in die System.Data.Common Objekte konvertieren, um etwas weiter zu kommen .. DbTransaction, DbConnection, DbCommand etc).

Was ich gerne wissen würde ist in erster Linie, ob es möglich ist, eine Transaktion auf diese Weise zu verspotten, oder ob ich hier den falschen Baum belle. Glücklicherweise kann ich die Schnittstelle möglicherweise so konvertieren, dass sie einen generischen DbTransaction-Parameter anstelle der providerspezifischen SqlTransaction verwendet, aber ich bin nicht davon überzeugt, dass es mir deshalb so schwer fällt, mich zu verspotten.

Hier ist (und dies könnte völlig falsch sein, so bitte korrigieren Sie mich, oder einen Kommentar, wenn ich diese incorretly bin nähert), was ich für spöttische Code so weit gekommen ...

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

jedoch diese scheint ein Argument auf der mockCommand.Setup Linie zu werfen .. (Ungültige Setup auf einem nicht-overridable Mitglied)

Hat jemand irgendwelche Ideen oder Vorschläge, wie ich diese Methode der Lage sein, könnte richtig Unit-Test mit einem zu verspottet SqlTransaction Parameter?

Antwort

3

Peter, ich werde SqlServer-Backend übernehmen.

Als Erstes würde ich MyMethod() umgestalten, indem ich den Typ des Eingangsparameters in IDbTransaction auf MyMethod() ändere, so dass Sie Ihren Mock übergeben können. Dann würde ich den Aufruf ExecuteScalar() abstrahieren, so dass ich die SqlCommand-Parameter in meinem Test einfach überprüfen könnte.

Da Moq unterstützt rekursive spöttisch, im Test ich den Anruf auf sharedTransaction wie diese verspotten würde:

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

eine weitere mock Erstellen Sie die Befehlsparameter in Ihrem Verfahren zur Überprüfung und einen Wert auf der ExecuteScalar Rückkehr() Ruf an, und du bist fertig!

Überarbeitete 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); 
} 

Der Test:

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

} 

Persönlich Ich mag die Parameter auf meine SqlCommand Anrufe testen, so griff ich Ihr Problem aus diesem Blickwinkel in meinem Beispiel .

+0

Ich habe immer noch damit zu kämpfen. Ich fange an zu vermuten, dass ich nur ein grundlegendes Konzept vermisse, das Datenbankinteraktionen spottet. Woher kommt die Klasse "IDbUtility", die Sie im obigen Testcode verwenden? (Ich konnte eine Weile weiterkommen, komme aber jetzt wieder auf ein ähnliches Problem zurück ..) –

+0

Ich denke, es würde hier helfen, Ihre MyMethod() -Methode zu zerlegen und herauszufinden, was Sie versuchen zu testen. Es sieht aus wie Sie überprüfen möchten, dass MyMethod() ruft: SharedTransaction.Connection.CreateCommand() Basierend auf dem Code, den Sie geschrieben haben, möchten Sie überprüfen, ob das SqlCommand() - Objekt den richtigen Befehlstext hat. ParamName wurde mit Wert 123 eingeschlossen und ExecuteScalar() wird aufgerufen. Wenn Sie versuchen, den Test von der Datenbank zu trennen, müssen Sie einen Schein für den Aufruf ExecuteScalar() übergeben. Ansonsten gehst du in die Datenbank. –

Verwandte Themen