找到解决方案后的简短内容:
AutoFixture 返回冻结的模拟就好了;我的 sut 也是由 AutoFixture 生成的,它只有一个公共属性,该属性具有对测试很重要的本地默认值,并且 AutoFixture 设置为新值。从马克的回答中可以学到很多东西。
原始问题:
我昨天开始为我的 xUnit.net 测试开始尝试 AutoFixture,这些测试中都有 Moq。我希望替换一些 Moq 的东西或使它更易于阅读,我对在 SUT Factory 容量中使用 AutoFixture 特别感兴趣。
我用 Mark Seemann 的一些关于 AutoMocking 的博客文章武装自己,并尝试从那里开始工作,但我并没有走得太远。
这是我的测试在没有 AutoFixture 的情况下的样子:
[Fact]
public void GetXml_ReturnsCorrectXElement()
{
// Arrange
string xmlString = @"
<mappings>
<mapping source='gcnm_loan_amount_min' target='gcnm_loan_amount_min_usd' />
<mapping source='gcnm_loan_amount_max' target='gcnm_loan_amount_max_usd' />
</mappings>";
string settingKey = "gcCreditApplicationUsdFieldMappings";
Mock<ISettings> settingsMock = new Mock<ISettings>();
settingsMock.Setup(s => s.Get(settingKey)).Returns(xmlString);
ISettings settings = settingsMock.Object;
ITracingService tracing = new Mock<ITracingService>().Object;
XElement expectedXml = XElement.Parse(xmlString);
IMappingXml sut = new SettingMappingXml(settings, tracing);
// Act
XElement actualXml = sut.GetXml();
// Assert
Assert.True(XNode.DeepEquals(expectedXml, actualXml));
}
这里的故事很简单 - 确保使用正确的键(硬编码/属性注入)SettingMappingXml
查询ISettings
依赖项并将结果作为XElement
. 仅当ITracingService
出现错误时才相关。
我试图做的是摆脱显式创建ITracingService
对象然后手动注入依赖项的需要(不是因为这个测试太复杂,而是因为它足够简单,可以尝试并理解它们)。
输入 AutoFixture - 第一次尝试:
[Fact]
public void GetXml_ReturnsCorrectXElement()
{
// Arrange
IFixture fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
string xmlString = @"
<mappings>
<mapping source='gcnm_loan_amount_min' target='gcnm_loan_amount_min_usd' />
<mapping source='gcnm_loan_amount_max' target='gcnm_loan_amount_max_usd' />
</mappings>";
string settingKey = "gcCreditApplicationUsdFieldMappings";
Mock<ISettings> settingsMock = new Mock<ISettings>();
settingsMock.Setup(s => s.Get(settingKey)).Returns(xmlString);
ISettings settings = settingsMock.Object;
fixture.Inject(settings);
XElement expectedXml = XElement.Parse(xmlString);
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();
// Act
XElement actualXml = sut.GetXml();
// Assert
Assert.True(XNode.DeepEquals(expectedXml, actualXml));
}
我希望CreateAnonymous<SettingMappingXml>()
,在检测到ISettings
构造函数参数后,会注意到已经为该接口注册了一个具体实例并注入了它——但是,它并没有这样做,而是创建了一个新的匿名实现。
这尤其令人困惑,因为fixture.CreateAnonymous<ISettings>()
确实返回了我的实例-
IMappingXml sut = new SettingMappingXml(fixture.CreateAnonymous<ISettings>(), fixture.CreateAnonymous<ITracingService>());
使测试完全绿色,这条线正是我期望 AutoFixture 在实例化SettingMappingXml
.
然后是冻结组件的概念,所以我只是将模拟冻结在夹具中,而不是获取模拟对象:
fixture.Freeze<Mock<ISettings>>(f => f.Do(m => m.Setup(s => s.Get(settingKey)).Returns(xmlString)));
果然这工作得很好——只要我明确地调用SettingMappingXml
构造函数并且不依赖CreateAnonymous()
.
简而言之,我不明白为什么它会以明显的方式工作,因为它违背了我能想到的任何逻辑。通常我会怀疑库中存在错误,但这是一个非常基本的东西,我相信其他人会遇到这个问题,而且它早就被发现并修复了。更重要的是,知道 Mark 对测试和 DI 的孜孜不倦的态度,这不可能是无意的。
这反过来意味着我必须错过一些相当基本的东西。如何让 AutoFixture 创建我的 SUT,并将预配置的模拟对象作为依赖项?我现在唯一确定的是我需要 .AutoMoqCustomization
所以我不必为ITracingService
.
AutoFixture/AutoMoq 包是 2.14.1,Moq 是 3.1.416.3,都来自 NuGet。.NET 版本为 4.5(与 VS2012 一起安装),在 VS2012 和 2010 中的行为相同。
在写这篇文章时,我发现有些人在使用 Moq 4.0 和程序集绑定重定向时遇到问题,所以我仔细清除了我的解决方案中的任何 Moq 4 实例,并通过将 AutoFixture.AutoMoq 安装到“干净”项目中来安装 Moq 3.1。但是,我的测试行为保持不变。
感谢您的任何指示和解释。
更新:这是 Mark 要求的构造函数代码:
public SettingMappingXml(ISettings settingSource, ITracingService tracing)
{
this._settingSource = settingSource;
this._tracing = tracing;
this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}
为了完整起见,该GetXml()
方法如下所示:
public XElement GetXml()
{
int errorCode = 10600;
try
{
string mappingSetting = this._settingSource.Get(this.SettingKey);
errorCode++;
XElement mappingXml = XElement.Parse(mappingSetting);
errorCode++;
return mappingXml;
}
catch (Exception e)
{
this._tracing.Trace(errorCode, e.Message);
throw;
}
}
SettingKey
只是一个自动属性。