如何使用Moq模拟SqlDataReader - 更新

问题描述:

我是新来的moq和设置模拟,所以我可以做一点帮助。如何使用Moq模拟SqlDataReader?如何使用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; 
} 

你的是,当我在我的GetTestData()方法做reader.Read它总是空的问题。我需要知道如何做类似的东西

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

+0

我没有模拟SqlDataReader的经验,但是,如果可以的话,你应该模拟接口。我已经查找了你,也许这篇文章可以帮助雅:] http://*.com/questions/1792984/mocking-a-datareader-and-getting-a-rhino-mocks-exceptions-expectationviolationexc它使用Rhinomocks但这个想法是一样的。建议那里,你应该模拟IDataReader。当你嘲笑它的时候,你不应该在模拟^^(^)上做任何问题^^如果你已经试着嘲笑一个接口,也许你可以通过发布一些示例代码告诉我们你卡在哪里:] – Bas 2010-04-15 09:13:22

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

完美谢谢我正在寻找的东西。 – lancscoder 2010-05-05 09:35:18

+1

为了防止这种情况对其他人有帮助,在Returns方法中()=>中的方括号是至关重要的......如果你不使用它们,你会发现自己陷入了无限循环 – Liath 2013-04-10 11:07:58

在一些测试问题尝试设置DataReader.Read()为true一个循环,然后将其设置为false。犀牛模拟有Repeat.Once()选项,但我无法在Moq中找到类似的方法(我可能在这里是错误的)。

测试这个的主要原因是将读取器转换为相关数据类型的扩展方法,所以最后我删除了while循环并只访问了我的模拟器中设置的值。代码如下:

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

不是一个理想的解决方案,但它的工作原理。如果有人知道最好留下评论谢谢。

我只是想弄明白自己。不知道这是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; 
} 

这不会让你嘲笑一个SqlDataReader但如果你的函数返回一个DbDataReader(基类的SqlDataReader)或IDataReader嘲笑它只是使用DataTable或easist方式一个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自定义工具(应该是默认有它)。这将导致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); 

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