How to mock an SqlDataReader using Moq - Update

lancscoder picture lancscoder · Apr 15, 2010 · Viewed 24.2k times · Source

I'm new to moq and setting up mocks so i could do with a little help. How do I mock up an SqlDataReader using Moq?

Update

After further testing this is what I have so far:

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

The issue you is when I do reader.Read in my GetTestData() method its always empty. I need to know how to do something like

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

as per the rhino mock example: Mocking a DataReader and getting a Rhino.Mocks.Exceptions.ExpectationViolationException: IDisposable.Dispose(); Expected #0, Actual #1

Answer

Monsignor picture Monsignor · May 5, 2010

Moq has an ability to run some code after the method is executed. It is called "Callback". Modify your code this way and it will work:

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

But what if it will be required IDataReader to contain not only single row, but several? Well, here is a sample:

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