Mocking a dependency with AutoFixture
-
21-12-2019 - |
Question
I've recently started using AutoFixture+AutoMoq and I'm trying to create an instance of Func<IDbConnection>
(i.e., a connection factory).
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var connectionFactory = fixture.Create<Func<IDbConnection>>();
This seems to work rather well:
- My system under test can call the delegate and it will get a mock of
IDbConnection
- On which I can then call
CreateCommand
, which will get me a mock ofIDbCommand
- On which I can then call
ExecuteReader
, which will get me a mock ofIDataReader
I now want to perform additional setups on the mock of IDataReader
, such as make it return true
when Read()
is called.
From what I've read, I should be using Freeze
for this:
var dataReaderMock = fixture.Freeze<Mock<IDataReader>>();
dataReaderMock.Setup(dr => dr.Read())
.Returns(true);
This doesn't seem to meet my expectations though. When I call IDbCommand.ExecuteReader
, I'll get a different reader than the one I just froze/setup.
Here's an example:
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var dataReaderMock = fixture.Freeze<Mock<IDataReader>>();
dataReaderMock.Setup(dr => dr.Read())
.Returns(true);
//true - Create<IDataReader> retrieves the data reader I just mocked
Assert.AreSame(dataReaderMock.Object, fixture.Create<IDataReader>());
//false - IDbCommand returns a different instance of IDataReader
Assert.AreSame(dataReaderMock.Object, fixture.Create<IDbCommand>().ExecuteReader());
What am I doing wrong? How do I get other fixtures, such as IDbCommand
, to use the mocked instance of IDataReader
?
Solution
As of 3.20.0, you can use AutoConfiguredMoqCustomization
. This will automatically configure all mocks so that their members' return values are generated by AutoFixture.
E.g., IDbConnetion.CreateCommand
will be automatically configured to return an IDbCommand
from the fixture, and IDbCommand.ExecuteReader
will be automatically configured to return an IDataReader
from the fixture.
All of these tests should pass now:
var fixture = new Fixture().Customize(new AutoConfiguredMoqCustomization());
var dataReaderMock = fixture.Freeze<Mock<IDataReader>>();
dataReaderMock.Setup(dr => dr.Read())
.Returns(true);
//all pass
Assert.Same(dataReaderMock.Object, fixture.Create<IDataReader>());
Assert.Same(dataReaderMock.Object, fixture.Create<IDbCommand>().ExecuteReader());
Assert.Same(dataReaderMock.Object, fixture.Create<IDbConnection>().CreateCommand().ExecuteReader());
Assert.Same(dataReaderMock.Object, fixture.Create<Func<IDbConnection>>()().CreateCommand().ExecuteReader());
OTHER TIPS
You have to Freeze
the Mock<IDbCommand>
as well – and setup the mock object (as a Stub) to return the existing dataReaderMock.Object
instance.
If you add the following to the Arrange phase of you test, the test will pass:
var dbCommandStub =
fixture
.Freeze<Mock<IDbCommand>>()
.Setup(x => x.ExecuteReader())
.Returns(dataReaderMock.Object);
While the solution from Nikos works I would not I do not recommend mocking ado.net.
In my opinion your tests will probably be hard to understand, maintain and will not give you the confidence your tests should give you.
I would consider testing your data layer by going all the way to the database even though it is slower.
I would recommend reading this article regarding best practices for mocking: http://codebetter.com/jeremymiller/2006/01/10/best-and-worst-practices-for-mock-objects/
Don't mock others: http://aspiringcraftsman.com/2012/04/01/tdd-best-practices-dont-mock-others/
I don't know your exact situation but anyways I wanted to share this.