代码之家  ›  专栏  ›  技术社区  ›  lancscoder

如何使用moq-update模拟sqldatareader

  •  33
  • lancscoder  · 技术社区  · 14 年前

    我对moq不太熟悉,所以我可以在一点帮助下设置mock。如何使用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;
    }
    

    当我读reader.read在我的gettestdata()方法中,它总是空的。我需要知道怎么做

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

    根据犀牛模型示例: Mocking a DataReader and getting a Rhino.Mocks.Exceptions.ExpectationViolationException: IDisposable.Dispose(); Expected #0, Actual #1

    5 回复  |  直到 5 年前
        1
  •  59
  •   Monsignor    14 年前

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

    但是,如果要求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;
    }
    
        2
  •  11
  •   mikesigs    8 年前

    我只是想自己弄明白。不确定这是否是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; 
    }
    
        3
  •  2
  •   Scott Chamberlain    7 年前

    这不让你嘲笑 SqlDataReader 但是如果你的函数返回 DbDataReader (的基类 数据阅读器 或A IDataReader 最简单的模仿方式就是用 DataTable 或A DataSet 并调用它的 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");
        }
    }
    

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

    然后添加 TestDataSetData.xml 作为测试项目的资源。最后在测试中创建 测试数据集 并打电话 ReadXml 使用您生成的XML文件中的文本。

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

    这将创建一个数据读卡器,其作用与从SQL查询返回的数据读卡器相同,甚至支持返回的多个结果集。

        4
  •  1
  •   lancscoder    14 年前

    在一些测试之后,问题是试图将一个循环的datareader.read()设置为true,然后将其设置为false。Rhino Mock有repeat.once()选项,但在moq中找不到类似的方法(这里我可能错了)。

    测试这一点的主要原因是扩展方法将读卡器转换为相关的数据类型,因此在最后,我删除了while循环,只访问了在mock中设置的值。代码如下:

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

    不是一个理想的解决方案,但它是有效的。如果有人知道,最好发表评论,谢谢。

        5
  •  0
  •   Kamil Stadryniak    5 年前

    灵感来源于@mikesigs answer和另一个问题: SetupSequence in Moq 我提出了以下扩展方法,可以为您工作:

        public static void SetupDataReader(this Mock<IDataReader> dataReaderMock, IList<string> columnNames, ICollection collection)
        {
            var queue = new Queue(collection);
    
            dataReaderMock
                .Setup(x => x.Read())
                .Returns(() => queue.Count > 0)
                .Callback(() =>
                {
                    if (queue.Count > 0)
                    {
                        var row = queue.Dequeue();
                        foreach (var columnName in columnNames)
                        {
                            var columnValue = row.GetType().GetProperty(columnName).GetValue(row);
                            dataReaderMock
                                .Setup(x => x[columnNames.IndexOf(columnName)])
                                .Returns(columnValue);
                            dataReaderMock
                                .Setup(x => x[columnName])
                                .Returns(columnValue);
                        }
                    }
                });
        }
    

    使用示例:

            var foundTargetIds = new[] { 1, 2, 3 };
            var dataReaderMock = new Mock<IDataReader>();
            dataReaderMock.SetupDataReader(new[] { "TargetId" }, foundTargetIds.Select(x => new { TargetId = x }).ToList());