2010-04-15 21 views
30

Sono nuovo a moq e sto configurando i mock quindi potrei fare un piccolo aiuto. Come faccio a mockare un SqlDataReader usando Moq?Come prendere in giro un SqlDataReader usando Moq - Aggiornamento

Aggiornamento

Dopo ulteriori test questo è quello che ho finora:

private IDataReader MockIDataReader() 
{ 
    var moq = new Mock<IDataReader>(); 
    moq.Setup(x => x.Read()).Returns(true); 
    moq.Setup(x => x.Read()).Returns(false); 
    moq.SetupGet<object>(x => x["Char"]).Returns('C'); 

    return moq.Object; 
} 

private class TestData 
{ 
    public char ValidChar { get; set; } 
} 

private TestData GetTestData() 
{ 
    var testData = new TestData(); 

    using (var reader = MockIDataReader()) 
    { 
     while (reader.Read()) 
     { 
      testData = new TestData 
      { 
       ValidChar = reader.GetChar("Char").Value 
      }; 
     } 
    } 

    return testData; 
} 

Il problema si è quando faccio reader.Read nel mio metodo GetTestData() la sua sempre vuoto. Ho bisogno di sapere come fare qualcosa di simile

reader.Stub(x => x.Read()).Repeat.Once().Return(true) 

come per esempio il rinoceronte finto: Mocking a DataReader and getting a Rhino.Mocks.Exceptions.ExpectationViolationException: IDisposable.Dispose(); Expected #0, Actual #1

+0

Non ho esperienza di simulazione di SqlDataReader ma, se è possibile, è necessario prendere in giro l'interfaccia. Ho cercato per te e forse questo articolo potrebbe aiutarti: http://stackoverflow.com/questions/1792984/mocking-a-datareader-and-getting-a-rhino-mocks-exceptions-expectationviolationexc Usa Rhinomocks ma l'idea è la stessa. Suggerito lì, dovresti simulare IDataReader. Quando lo prendi in giro, non dovresti avere problemi a fare .Setups() sul finto ^^ Se hai già provato a usare un'interfaccia, forse potresti mostrarci dove ti blocchi mettendo un codice di esempio:] – Bas

risposta

54

Moq ha la capacità di eseguire codice dopo che il metodo è stato eseguito. Si chiama "Callback". modificare il codice in questo modo e che funzionerà:

private IDataReader MockIDataReader() 
{ 
    var moq = new Mock<IDataReader>(); 

    bool readToggle = true; 

    moq.Setup(x => x.Read()) 
     // Returns value of local variable 'readToggle' (note that 
     // you must use lambda and not just .Returns(readToggle) 
     // because it will not be lazy initialized then) 
     .Returns(() => readToggle) 
     // After 'Read()' is executed - we change 'readToggle' value 
     // so it will return false on next calls of 'Read()' 
     .Callback(() => readToggle = false); 

    moq.Setup(x => x["Char"]) 
     .Returns('C'); 

    return moq.Object; 
} 

private class TestData 
{ 
    public char ValidChar { get; set; } 
} 

private TestData GetTestData() 
{ 
    var testData = new TestData(); 

    using (var reader = MockIDataReader()) 
    { 
     testData = new TestData 
     { 
      ValidChar = (Char)reader["Char"] 
     }; 
    } 

    return testData; 
} 

Ma cosa succede se sarà necessario IDataReader per contenere non solo singola riga, ma diversi? Bene, ecco un esempio:

// You should pass here a list of test items, their data 
// will be returned by IDataReader 
private IDataReader MockIDataReader(List<TestData> ojectsToEmulate) 
{ 
    var moq = new Mock<IDataReader>(); 

    // This var stores current position in 'ojectsToEmulate' list 
    int count = -1; 

    moq.Setup(x => x.Read()) 
     // Return 'True' while list still has an item 
     .Returns(() => count < ojectsToEmulate.Count - 1) 
     // Go to next position 
     .Callback(() => count++); 

    moq.Setup(x => x["Char"]) 
     // Again, use lazy initialization via lambda expression 
     .Returns(() => ojectsToEmulate[count].ValidChar); 

    return moq.Object; 
} 
+0

Perfetto grazie proprio quello che stavo cercando. – lancscoder

+1

Nel caso in cui questo aiuti qualcun altro, nel metodo Resi le parentesi in() => sono critiche ... se non le usi ti troverai in un ciclo infinito – Liath

1

Dopo qualche test del problema sta cercando di impostare il DataReader.Read() su true per un loop e poi impostandolo su false. Rhino Mock ha l'opzione Repeat.Once() ma non ho trovato un metodo simile in Moq (potrei sbagliarmi qui).

La ragione principale per la prova che questo era i metodi di estensione per convertire il lettore al tipo di dati rilevanti in modo, alla fine ho rimosso il ciclo while e appena entrato i valori che erano stati di installazione nella mia finto. Il codice appare come segue:

private IDataReader MockIDataReader() 
{ 
    var moq = new Mock<IDataReader>(); 
    moq.SetupGet<object>(x => x["Char"]).Returns('C'); 

    return moq.Object; 
} 

private class TestData 
{ 
    public char ValidChar { get; set; } 
} 

private TestData GetTestData() 
{ 
    var testData = new TestData(); 

    using (var reader = MockIDataReader()) 
    { 
     testData = new TestData 
     { 
      ValidChar = reader.GetChar("Char").Value 
     }; 
    } 

    return testData; 
} 

Non è una soluzione ideale ma funziona. Se qualcuno sa meglio lasciare un commento grazie.

8

Stavo solo cercando di capire questo fuori me stesso. Non sono sicuro se questa è una nuova funzionalità in Moq, ma sembra che ci sia un modo più semplice della risposta di @ Monsignor.

Utilizzare il metodo Moq SetupSequence. Il tuo codice diventa semplicemente:

private IDataReader MockIDataReader() 
{ 
    var moq = new Mock<IDataReader>(); 
    moq.SetupSequence(x => x.Read()) 
     .Returns(true); 
     .Returns(false); 
    moq.SetupGet<object>(x => x["Char"]).Returns('C'); 

    return moq.Object; 
} 
1

Questo non consente di deridere un SqlDataReader ma se la funzione restituisce un (classe La base del SqlDataReader) DbDataReader o un IDataReader il modo easist per deridere si tratta solo di usare un DataTable o a DataSet e chiamare la sua funzione CreateDataReader() e restituirlo.

Innanzitutto, in un progetto separato, eseguire la query come normale per produrre alcuni dati di test e utilizzare WriteXmlSchema per generare un file .xsd e le funzioni WriteXml per contenere i dati di test.

using (var con = new SqlConnection(connectionString)) 
{ 
    con.Open(); 
    using (var cmd = new SqlCommand("Some query", con)) 
    { 

     DataSet ds = new DataSet("TestDataSet"); 
     DataTable dt = new DataTable("FirstSet"); 
     ds.Tables.Add(dt); 
     using (var reader = cmd.ExecuteReader()) 
     { 
      dt.Load(reader); 
     } 

     ds.WriteXmlSchema(@"C:\Temp\TestDataSet.xsd"); 
     ds.WriteXml(@"C:\Temp\TestDataSetData.xml"); 
    } 
} 

Nel progetto di test aggiungere TestDataSet.xsd al progetto e assicurarsi che ha lo strumento personalizzato di MSDataSetGenerator (dovrebbe avere di default). Ciò causerà la generazione di una classe derivata DataTable denominata TestDataSet con lo schema della query.

Quindi aggiungere TestDataSetData.xml come risorsa al progetto di test.Infine, durante il test, creare TestDataSet e chiamare ReadXml utilizzando il testo del file xml generato.

var resultSet = new TestData.TestDataSet(); 
using (var reader = new StringReader(Resources.TestDataSetData)) 
{ 
    resultSet.ReadXml(reader); 
} 

var testMock = new Mock<DbCommand>(); 

testMock.Setup(x => x.ExecuteReader()) 
    .Returns(resultSet.CreateDataReader); 

testMock.Setup(x => x.ExecuteReaderAsync()) 
    .ReturnsAsync(resultSet.CreateDataReader); 

Questo creerà un lettore di dati che si comporterà esattamente come il lettore di dati che sarebbero stati restituiti dalla query SQL e supporta anche le cose come più set di risultati restituiti.

Problemi correlati