6

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:

  1. My system under test can call the delegate and it will get a mock of IDbConnection
  2. On which I can then call CreateCommand, which will get me a mock of IDbCommand
  3. On which I can then call ExecuteReader, which will get me a mock of IDataReader

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?

dcastro
  • 66,540
  • 21
  • 145
  • 155
  • Related: http://stackoverflow.com/a/18540861/126014 – Mark Seemann Jul 31 '14 at 15:21
  • You're basically seeing the implications of this: https://github.com/AutoFixture/AutoFixture/issues/176 – Mark Seemann Jul 31 '14 at 15:22
  • 1
    @MarkSeemann I see... Looking at the [`MockConfigurator` source](https://github.com/AutoFixture/AutoFixture/blob/master/Src/AutoMoq/MockPostprocessor.cs), I can see the mock's default value is being set to `DefaultValue.Mock`, and that's why `ExecuteReader` gets be a brand new mock of `IDataReader`. I'll see if I can create my own configurator to setup every method to make the mock call back into the `fixture` and get its return instance from the container. – dcastro Jul 31 '14 at 16:34

3 Answers3

8

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());
dcastro
  • 66,540
  • 21
  • 145
  • 155
4

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);
Nikos Baxevanis
  • 10,868
  • 2
  • 46
  • 80
1

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.

Jakob
  • 536
  • 4
  • 16
  • Thanks for your response. However, if I do go all the way to the database, then I wouldn't be writing unit tests anymore, I'd be writing functional tests. And I do have a comprehensive suite of functional tests to give me the confidence I need. – dcastro Aug 03 '14 at 18:50
  • 1
    Also, that post was written 8 years ago. It might have been hard to mock a database connection back then (hard enough to not seem worth it), but that's certainly not the case nowadays. Plus, when I'm done writing my plugin for AutoFixture, it'll be doable in no more than 2/3 lines of code. – dcastro Aug 03 '14 at 18:55
  • This is a general advice which I have from many books and articles. Maybe some of them are old but I would not think they are outdated. Here is an newer article on mocking others: http://aspiringcraftsman.com/2012/04/01/tdd-best-practices-dont-mock-others/ What do you gain from your tests? – Jakob Aug 03 '14 at 19:44
  • I would only do integration and maybe acceptance tests for my infrastructure code. But people disagree about this. If you feel that the test are not a burden and you are gaining anything from them you should maybe disregard my advice. – Jakob Aug 03 '14 at 19:54
  • 1
    That last link you posted makes a good point about assuming the behavior of third-party libraries. For that reason, I don't couple myself to NLog or ActiveMQ, for example. Instead, I use our own custom `ILogger` and `ISendAdapter` interfaces, and then provide test doubles for those. Regarding database connections though, since `IDbConnection` is not vendor-specific, and is a well understood interface, I feel like it's ok to provide test doubles for it. I don't have integration tests though, maybe I should... Thanks for your input. – dcastro Aug 03 '14 at 20:50