【问题标题】:Mocking a dependency with AutoFixture使用 AutoFixture 模拟依赖项
【发布时间】:2014-09-23 04:57:59
【问题描述】:

我最近开始使用 AutoFixture+AutoMoq,我正在尝试创建 Func<IDbConnection> 的实例(即连接工厂)。

var fixture = new Fixture().Customize(new AutoMoqCustomization());
var connectionFactory = fixture.Create<Func<IDbConnection>>();

这似乎工作得很好:

  1. 我的测试系统可以调用委托,它会得到一个模拟 IDbConnection
  2. 然后我可以在上面调用CreateCommand,这会让我模拟IDbCommand
  3. 然后我可以在上面调用ExecuteReader,这会让我模拟IDataReader

我现在想在 IDataReader 的模拟上执行其他设置,例如在调用 Read() 时使其返回 true

根据我的阅读,我应该为此使用Freeze

var dataReaderMock = fixture.Freeze<Mock<IDataReader>>();

dataReaderMock.Setup(dr => dr.Read())
                      .Returns(true);

这似乎不符合我的期望。当我打电话给IDbCommand.ExecuteReader 时,我会得到一个不同于我刚刚冻结/设置的阅读器。

这是一个例子:

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

我做错了什么?如何让其他设备(例如 IDbCommand)使用 IDataReader 的模拟实例?

【问题讨论】:

标签: c# unit-testing moq autofixture automoq


【解决方案1】:

从 3.20.0 开始,您可以使用 AutoConfiguredMoqCustomization。这将自动配置所有模拟,以便其成员的返回值由 AutoFixture 生成。

例如,IDbConnetion.CreateCommand 将自动配置为从灯具返回 IDbCommandIDbCommand.ExecuteReader 将自动配置为从灯具返回 IDataReader

所有这些测试现在都应该通过了:

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

【讨论】:

  • AutoConfiguredMoqCustomization 现已过时,AutoMoqCustomization 是首选。
【解决方案2】:

您还必须 Freeze Mock&lt;IDbCommand&gt; 并设置模拟对象(作为存根)以返回现有的 dataReaderMock.Object 实例。

如果您在测试的安排阶段添加以下内容,则测试将通过:

var dbCommandStub = 
    fixture
        .Freeze<Mock<IDbCommand>>()
        .Setup(x => x.ExecuteReader())
        .Returns(dataReaderMock.Object);

【讨论】:

  • 这是否意味着我还必须冻结IDbConnection 并将其设置为返回dbCommandStub.Object
  • 是的,如果您从那里访问DbCommand 对象,您将不得不Freeze IDbConnection
【解决方案3】:

虽然 Nikos 的解决方案有效,但我不建议嘲笑 ado.net。

在我看来,您的测试可能难以理解、维护,并且不会给您测试应有的信心。

我会考虑通过一直到数据库来测试您的数据层,即使它比较慢。

我建议阅读这篇关于模拟最佳实践的文章: http://codebetter.com/jeremymiller/2006/01/10/best-and-worst-practices-for-mock-objects/

不要嘲笑别人: http://aspiringcraftsman.com/2012/04/01/tdd-best-practices-dont-mock-others/

我不知道你的具体情况,但无论如何我想分享一下。

【讨论】:

  • 感谢您的回复。但是,如果我真的一直到数据库,那么我就不会再编写单元测试了,我会编写功能测试。而且我确实有一套全面的功能测试来给我所需的信心。
  • 另外,那篇文章是 8 年前写的。那时可能很难模拟数据库连接(很难看起来不值得),但现在情况肯定不是这样。另外,当我为 AutoFixture 编写完插件后,它只需要 2/3 行代码即可完成。
  • 这是我从许多书籍和文章中得到的一般性建议。也许其中一些是旧的,但我不认为它们已经过时了。这是一篇关于嘲笑他人的较新文章:aspiringcraftsman.com/2012/04/01/…您从测试中获得了什么?
  • 我只会对我的基础架构代码进行集成和验收测试。但人们对此持不同意见。如果您觉得测试不是负担,并且您从中获得了任何东西,那么您可能应该无视我的建议。
  • 您发布的最后一个链接很好地说明了假设第三方库的行为。出于这个原因,例如,我不会将自己耦合到 NLog 或 ActiveMQ。相反,我使用我们自己的自定义ILoggerISendAdapter 接口,然后为它们提供测试替身。不过,关于数据库连接,由于IDbConnection 不是特定于供应商的,并且是一个很好理解的接口,我觉得为它提供测试替身是可以的。不过,我没有集成测试,也许我应该...感谢您的意见。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-27
  • 1970-01-01
  • 2020-12-09
  • 1970-01-01
  • 1970-01-01
  • 2012-03-20
相关资源
最近更新 更多