![](/img/trans.png)
[英]AutoFixture AutoMoq problem getting CallBase to work with injected Mock dependencies
[英]AutoFixture/AutoMoq ignores injected instance/frozen mock
現在找到解決方案的簡短內容:
AutoFixture返回凍結模擬就好了; 我的sut也是由AutoFixture生成的,只有一個公共屬性,其本地默認值對於測試非常重要,並且AutoFixture設置為新值。 除了Mark的答案之外,還有很多值得學習的東西。
原始問題:
我昨天開始嘗試使用AutoFixture進行我的xUnit.net測試,這些測試中包含了Moq。 我希望更換一些Moq的東西或者讓它更容易閱讀,我特別感興趣的是在SUT工廠容量中使用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>());
使測試完全變為綠色,這一行正是我在實例化SettingMappingXml
時所期望的AutoFixture內部做的。
然后是凍結組件的概念,所以我繼續只是凍結了夾具中的模擬而不是獲取模擬對象:
fixture.Freeze<Mock<ISettings>>(f => f.Do(m => m.Setup(s => s.Get(settingKey)).Returns(xmlString)));
果然這完全正常 - 只要我明確調用SettingMappingXml
構造函數並且不依賴於CreateAnonymous()
。
簡單地說,我不明白它為什么會以它顯然的方式工作,因為它違背了我能想到的任何邏輯。 通常情況下,我會懷疑庫中有一個錯誤,但這是一個非常基本的東西,我相信其他人會遇到這個問題,而且很久以來就會發現並修復它。 更重要的是,了解馬克對測試和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和程序集綁定重定向的問題,所以我通過將AutoFixture.AutoMoq安裝到“干凈”的項目中,精心清除了Moq 4的任何實例的解決方案並安裝了Moq 3.1。 但是,我的測試行為保持不變。
感謝您的任何指示和解釋。
更新:這是Mark要求的構造函數代碼:
public SettingMappingXml(ISettings settingSource, ITracingService tracing)
{
this._settingSource = settingSource;
this._tracing = tracing;
this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}
為了完整GetXml()
, 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
只是一個自動屬性。
假設SettingKey
屬性定義如下,我現在可以重現該問題:
public string SettingKey { get; set; }
注意到注入SettingMappingXml實例的Test Doubles完全沒問題,但由於SettingKey
是可寫的,因此AutoFixture的自動屬性功能會啟動並修改該值。
考慮以下代碼:
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var sut = fixture.CreateAnonymous<SettingMappingXml>();
Console.WriteLine(sut.SettingKey);
這打印出這樣的東西:
SettingKey83b75965-2886-4308-bcc4-eb0f8e63de09
即使所有測試雙打都已正確注入,但不滿足Setup
方法的期望。
有很多方法可以解決這個問題。
保護不變量
解決此問題的正確方法是使用單元測試和AutoFixture作為反饋機制。 這是GOOS的關鍵點之一:單元測試的問題通常是設計缺陷的症狀,而不是單元測試(或AutoFixture)本身的錯誤。
在這種情況下,它向我表明設計不夠萬無一失 。 客戶端可以隨意操作SettingKey
是否合適?
作為最低限度,我建議使用這樣的替代實現:
public string SettingKey { get; private set; }
隨着這種變化,我的責備通過了。
省略SettingKey
如果您不能(或不會)更改設計,可以指示AutoFixture跳過設置SettingKey
屬性:
IMappingXml sut = fixture
.Build<SettingMappingXml>()
.Without(s => s.SettingKey)
.CreateAnonymous();
就個人而言,每次我需要一個特定類的實例時,我發現必須編寫一個Build
表達式會適得其反。 您可以從實際實例化中分離如何創建SettingMappingXml
實例:
fixture.Customize<SettingMappingXml>(
c => c.Without(s => s.SettingKey));
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();
更進一步,您可以將Customize
方法調用封裝在Customization中 。
public class SettingMappingXmlCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<SettingMappingXml>(
c => c.Without(s => s.SettingKey));
}
}
這要求您使用該自定義創建Fixture
實例:
IFixture fixture = new Fixture()
.Customize(new SettingMappingXmlCustomization())
.Customize(new AutoMoqCustomization());
一旦您獲得超過兩個或三個自定義項鏈,您可能會厭倦一直編寫該方法鏈。 是時候將這些自定義封裝到特定庫的一組約定中:
public class TestConventions : CompositeCustomization
{
public TestConventions()
: base(
new SettingMappingXmlCustomization(),
new AutoMoqCustomization())
{
}
}
這使您始終可以像這樣創建Fixture
實例:
IFixture fixture = new Fixture().Customize(new TestConventions());
TestConventions
為您提供了一個中心位置,您可以在需要時隨時修改測試套件的約定。 它降低了單元測試的可維護性稅,並有助於使生產代碼的設計更加一致。
最后,因為它看起來好像你正在使用xUnit.net,你可以利用AutoFixture的xUnit.net集成 ,但在你這樣做之前,你需要使用一種不那么強制的操作Fixture
。 事實證明,創建,配置和注入ISettings
Test Double的代碼非常慣用,它有一個名為Freeze的快捷方式:
fixture.Freeze<Mock<ISettings>>()
.Setup(s => s.Get(settingKey)).Returns(xmlString);
有了這個,下一步是定義一個自定義AutoDataAttribute:
public class AutoConventionDataAttribute : AutoDataAttribute
{
public AutoConventionDataAttribute()
: base(new Fixture().Customize(new TestConventions()))
{
}
}
您現在可以將測試減少到最基本的部分,消除所有噪音,使測試能夠簡潔地表達重要的內容:
[Theory, AutoConventionData]
public void ReducedTheory(
[Frozen]Mock<ISettings> settingsStub,
SettingMappingXml sut)
{
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";
settingsStub.Setup(s => s.Get(settingKey)).Returns(xmlString);
XElement actualXml = sut.GetXml();
XElement expectedXml = XElement.Parse(xmlString);
Assert.True(XNode.DeepEquals(expectedXml, actualXml));
}
其他選擇
要使原始測試通過,您還可以完全關閉自動屬性:
fixture.OmitAutoProperties = true;
在第一個測試中,您可以使用AutoMoqCustomization
創建Fixture
類的實例:
var fixture = new Fixture()
.Customize(new AutoMoqCustomization());
然后,唯一的變化是:
第1步
// The following line:
Mock<ISettings> settingsMock = new Mock<ISettings>();
// Becomes:
Mock<ISettings> settingsMock = fixture.Freeze<Mock<ISettings>>();
第2步
// The following line:
ITracingService tracing = new Mock<ITracingService>().Object;
// Becomes:
ITracingService tracing = fixture.Freeze<Mock<ITracingService>>().Object;
第3步
// The following line:
IMappingXml sut = new SettingMappingXml(settings, tracing);
// Becomes:
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();
而已!
下面是它的工作原理:
在內部, Freeze
創建所請求類型的實例(例如Mock<ITracingService>
),然后將其注入 ,以便在您再次請求時始終返回該實例。
這就是我們在
Step 1
和Step 2
所做的。
在Step 3
我們請求一個SettingMappingXml
類型的實例,它依賴於ISettings
和ITracingService
。 由於我們使用Auto Mocking, Fixture
類將為這些接口提供模擬。 但是,我們之前已經為它們注入了Freeze
因此現在已經自動提供已經創建的模擬。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.