如何使用Moq模拟SqlDataReader - 更新

lan*_*der 36 c# unit-testing moq mocking

我是moq的新手,并设置了模拟,所以我可以做一些帮助.如何使用Moq模拟SqlDataReader?

更新

经过进一步测试,这是我到目前为止所做的:

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;
}
Run Code Online (Sandbox Code Playgroud)

你做读者时的问题.在我的GetTestData()方法中,它总是空的.我需要知道如何做类似的事情

reader.Stub( x => x.Read() ).Repeat.Once().Return( true ) 
Run Code Online (Sandbox Code Playgroud)

按照犀牛模拟例如:惩戒一个DataReader和得到一个Rhino.Mocks.Exceptions.ExpectationViolationException:IDisposable.Dispose(); 预期#0,实际#1

Mon*_*nor 61

Moq能够在执行方法后运行一些代码.它被称为"回调".以这种方式修改您的代码,它将工作:

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;
}
Run Code Online (Sandbox Code Playgroud)

但是如果需要IDataReader不仅要包含单行,还要包含几行呢?好吧,这是一个示例:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 以防万一这对其他人有帮助,在 Returns 方法中, () =&gt; 中的括号很重要……如果您不使用它们,您会发现自己处于无限循环中 (2认同)

mik*_*igs 12

我只是想弄清楚自己.不确定这是否是Moq中的新功能,但似乎有一种比@ Monsignor的答案更简单的方法.

使用Moq的SetupSequence方法.你的代码变成了:

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; 
}
Run Code Online (Sandbox Code Playgroud)


Sco*_*ain 6

这不会让您模拟 aSqlDataReader但如果您的函数返回 a DbDataReader( 的基类SqlDataReader)或IDataReader模拟它的简单方法就是使用 aDataTable或 aDataSet并调用其CreateDataReader()函数并返回它。

首先,在一个单独的项目中,像往常一样运行查询以生成一些测试数据,并使用WriteXmlSchema生成 .xsd 文件和WriteXml保存测试数据的函数。

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的测试项目中添加TestDataSet.xsd到项目并确保它具有自定义工具MSDataSetGenerator(默认情况下应该有它)。这将导致生成具有查询架构的DataTable命名派生类TestDataSet

然后TestDataSetData.xml作为资源添加到您的测试项目中。最后在您的测试中使用您生成的 xml 文件中的文本创建TestDataSet和调用ReadXml

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);
Run Code Online (Sandbox Code Playgroud)

这将创建一个数据读取器,它的行为就像从 sql 查询返回的数据读取器一样,甚至支持返回多个结果集之类的东西。