c# - verifiable - Cómo simular un SqlDataReader usando Moq-Actualización
verify moq (4)
Soy nuevo en moq y montaje de simulacros, así que podría hacerlo con un poco de ayuda. ¿Cómo simulo un SqlDataReader usando Moq?
Actualizar
Después de más pruebas, esto es lo que tengo hasta ahora:
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;
}
El problema es cuando hago el lector. Leer en mi método GetTestData () siempre está vacío. Necesito saber cómo hacer algo como
reader.Stub( x => x.Read() ).Repeat.Once().Return( true )
según el ejemplo falso del rinoceronte: burlarse de un DataReader y obtener Rhino.Mocks.Exceptions.ExpectationViolationException: IDisposable.Dispose (); Esperado # 0, real # 1
Después de algunas pruebas, el problema es tratar de establecer el DataReader.Read () en verdadero para un ciclo y luego establecerlo en falso. Rhino Mock tiene la opción Repeat.Once () pero no pude encontrar un método similar en Moq (podría estar equivocado aquí).
La razón principal para probar esto fueron los métodos de extensión para convertir el lector al tipo de datos relevante, así que al final eliminé el ciclo while y acabo de acceder a los valores que se habían configurado en mi simulacro. El código se ve de la siguiente manera:
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;
}
No es una solución ideal, pero funciona. Si alguien sabe mejor deje un comentario gracias.
Esto no le permite SqlDataReader
un SqlDataReader
pero si su función devuelve un DbDataReader
(La clase base de SqlDataReader
) o un IDataReader
la forma más fácil de burlarse es simplemente usar un DataTable
o un DataSet
y llamar a su función CreateDataReader()
y devolver ese .
Primero, en un proyecto separado, ejecute su consulta de forma normal para producir algunos datos de prueba y use WriteXmlSchema
para generar un archivo .xsd y las funciones WriteXml
para contener los datos de prueba.
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");
}
}
En su proyecto de prueba, agregue TestDataSet.xsd
al proyecto y asegúrese de que tenga la herramienta personalizada de MSDataSetGenerator
(debería tenerlo de forma predeterminada). Esto causará que se genere una clase derivada DataTable
llamada TestDataSet
que tenga el esquema de su consulta.
A continuación, agregue TestDataSetData.xml
como un recurso para su proyecto de prueba. Finalmente, en su prueba, cree el TestDataSet
y llame a ReadXml
usando el texto del archivo xml que generó.
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);
Esto creará un lector de datos que actuará como el lector de datos que se hubiera devuelto de la consulta SQL e incluso admite cosas como múltiples conjuntos de resultados devueltos.
Moq tiene la capacidad de ejecutar algún código después de que se ejecuta el método. Se llama "devolución de llamada". Modifique su código de esta manera y funcionará:
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;
}
Pero, ¿y si se requiere que IDataReader contenga no solo una fila, sino varias? Bueno, aquí hay una muestra:
// 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;
}
Solo estaba tratando de resolver esto yo mismo. No estoy seguro de si esta es una nueva funcionalidad en Moq, pero parece que hay una manera más simple que la respuesta de @Monignor.
Usa el método Moq SetupSequence
. Tu código simplemente se convierte en:
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;
}