AutoFixture / AutoMoq ignoruje instancję wtrysku / zamrożoną próbkę
Krótkie jedzenie teraz, gdy rozwiązanie zostało znalezione:
AutoFixture zwraca zamrożenie makiety w porządku; mój szew, który również został wygenerowany przez AutoFixture, miał po prostu właściwość publiczną z lokalną wartością domyślną, która była ważna dla testu, a ta AutoFixture została ustawiona na nową wartość. Poza odpowiedzią Marka można się wiele nauczyć.
Oryginalne pytanie:
Wczoraj zacząłem wypróbowywać AutoFixture dla moich testów xUnit.net, które mają na sobie Moqa. Miałem nadzieję, że zastąpię niektóre rzeczy Moqa lub sprawię, że będą łatwiejsze do przeczytania, a szczególnie interesuje mnie użycie AutoFixture w pojemności fabryki SUT.
Uzbroiłem się w kilka postów na blogu Marka Seemanna na temat AutoMockingu i próbowałem stamtąd pracować, ale nie zaszedłem daleko.
Tak wyglądał mój test bez 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));
}
Historia tutaj jest dość prosta - upewnij się, żeSettingMappingXml
pytaISettings
zależność z poprawnym kluczem (który jest zakodowany / wstrzyknięty) i zwraca wynik jakoXElement
. TheITracingService
ma znaczenie tylko w przypadku błędu.
Próbowałem pozbyć się potrzeby jawnego tworzeniaITracingService
obiekt, a następnie ręcznie wstrzyknij zależności (nie dlatego, że ten test jest zbyt złożony, ale ponieważ jest wystarczająco prosty, aby wypróbować i zrozumieć je).
Enter AutoFixture - pierwsza próba:
[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));
}
oczekiwałbymCreateAnonymous<SettingMappingXml>()
, po wykryciuISettings
parametr konstruktora, aby zauważyć, że konkretna instancja została zarejestrowana dla tego interfejsu i wstrzyknąć ją - jednak nie robi tego, ale zamiast tego tworzy nową anonimową implementację.
Jest to szczególnie mylące, ponieważfixture.CreateAnonymous<ISettings>()
rzeczywiście zwraca moją instancję -
IMappingXml sut = new SettingMappingXml(fixture.CreateAnonymous<ISettings>(), fixture.CreateAnonymous<ITracingService>());
sprawia, że test jest idealnie zielony, a ta linia jest dokładnie tym, czego oczekiwałem, gdy AutoFixture będzie wewnętrznie robił wystąpienieSettingMappingXml
.
Potem jest koncepcja zamrożenia komponentu, więc poszedłem dalej, zamroziłem próbkę w urządzeniu, zamiast uzyskiwać wyszydzony obiekt:
fixture.Freeze<Mock<ISettings>>(f => f.Do(m => m.Setup(s => s.Get(settingKey)).Returns(xmlString)));
Na pewno działa to doskonale - tak długo, jak nazywamSettingMappingXml
konstruktor jawnie i nie polegaj naCreateAnonymous()
.
Mówiąc najprościej, nie rozumiem, dlaczego działa tak, jak się wydaje, ponieważ jest sprzeczne z jakąkolwiek logiką, którą mogę wyczarować. Normalnie podejrzewałbym błąd w bibliotece, ale jest to coś tak podstawowego, że jestem pewien, że inni wpadliby na to i długo by go znaleziono i naprawiono. Co więcej, znając wytrwałe podejście Marka do testowania i DI, nie może to być niezamierzone.
To z kolei oznacza, że brakuje mi czegoś raczej elementarnego. Jak mogę utworzyć mój SUT przez AutoFixture z prekonfigurowanym wyszydzonym obiektem jako zależnością? Jedyne, czego teraz jestem pewien, to że potrzebujęAutoMoqCustomization
więc nie muszę niczego konfigurować dlaITracingService
.
Pakiety AutoFixture / AutoMoq to 2.14.1, Moq to 3.1.416.3, wszystkie z NuGet. Wersja .NET jest 4.5 (zainstalowana z VS2012), zachowanie jest takie samo w VS2012 i 2010.
Podczas pisania tego posta odkryłem, że niektórzy ludzie mają problemy z przekierowaniami Moq 4.0 i składania, więc skrupulatnie oczyściłem moje rozwiązanie z wszystkich instancji Moq 4 i zainstalowałem Moq 3.1, instalując AutoFixture.AutoMoq w „czystych” projektach. Jednak zachowanie mojego testu pozostaje niezmienione.
Dziękujemy za wszelkie wskazówki i wyjaśnienia.
Aktualizacja: Oto kod konstruktora Mark zapytany o:
public SettingMappingXml(ISettings settingSource, ITracingService tracing)
{
this._settingSource = settingSource;
this._tracing = tracing;
this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}
I dla kompletnościGetXml()
metoda wygląda tak:
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
to po prostu automatyczna właściwość.