AutoFixture / AutoMoq ignoriert injizierte Instanzen / eingefrorene Mocks

Das kurze Mitnehmen, nachdem die Lösung gefunden wurde:

AutoFixture gibt den gefrorenen Schein gut zurück; Mein Sut, das ebenfalls von AutoFixture generiert wurde, hatte nur eine öffentliche Eigenschaft mit einem lokalen Standardwert, der für den Test wichtig war, und dieser AutoFixture wurde auf einen neuen Wert gesetzt. Darüber hinaus gibt es viel von Marks Antwort zu lernen.

Ursprüngliche Frage:

Ich habe gestern angefangen, AutoFixture für meine xUnit.net-Tests auszuprobieren, auf denen Moq zu finden ist. Ich hatte gehofft, einige der Moq-Elemente zu ersetzen oder das Lesen zu vereinfachen, und ich bin besonders daran interessiert, AutoFixture in der SUT Factory-Kapazität zu verwenden.

Ich habe mich mit einigen Blogbeiträgen von Mark Seemann zu AutoMocking bewaffnet und versucht, von dort aus zu arbeiten, bin aber nicht weit gekommen.

So sah mein Test ohne AutoFixture aus:

[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));
}

Die Geschichte hier ist einfach genug - stellen Sie sicher, dassSettingMappingXml fragt dieISettings Abhängigkeit mit dem richtigen Schlüssel (der fest codiert / Eigenschaft injiziert ist) und gibt das Ergebnis alsXElement. DasITracingService ist nur relevant, wenn ein Fehler vorliegt.

Ich habe versucht, das explizit zu erstellenITracingService objektieren und dann die Abhängigkeiten manuell einfügen (nicht weil dieser Test zu komplex ist, sondern weil es einfach genug ist, Dinge auszuprobieren und zu verstehen).

AutoFixture aufrufen - erster Versuch:

[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));
}

Ich würde erwartenCreateAnonymous<SettingMappingXml>()bei Entdeckung desISettings Konstruktorparameter, um festzustellen, dass eine konkrete Instanz für diese Schnittstelle registriert wurde, und um diese zu injizieren. Dies wird jedoch nicht durchgeführt, sondern stattdessen eine neue anonyme Implementierung erstellt.

Dies ist als besonders verwirrendfixture.CreateAnonymous<ISettings>() gibt in der Tat meine Instanz zurück -

IMappingXml sut = new SettingMappingXml(fixture.CreateAnonymous<ISettings>(), fixture.CreateAnonymous<ITracingService>());

macht den Test perfekt grün und diese Linie ist genau das, was ich von AutoFixture erwartet hatte, als es intern instanziiert wurdeSettingMappingXml.

Dann gibt es das Konzept, eine Komponente einzufrieren, also habe ich einfach den Schein in der Leuchte eingefroren, anstatt das verspottete Objekt zu erhalten:

fixture.Freeze<Mock<ISettings>>(f => f.Do(m => m.Setup(s => s.Get(settingKey)).Returns(xmlString)));

Klar funktioniert das einwandfrei - solange ich das anrufeSettingMappingXml Konstruktor explizit und verlassen sich nicht aufCreateAnonymous().



Einfach ausgedrückt, ich verstehe nicht, warum es so funktioniert, wie es anscheinend funktioniert, da es gegen jede Logik verstößt, die ich heraufbeschwören kann. Normalerweise würde ich einen Fehler in der Bibliothek vermuten, aber das ist etwas so Grundlegendes, dass ich mir sicher bin, dass andere darauf gestoßen wären und es schon lange gefunden und behoben worden wäre. Darüber hinaus kann dies nicht unbeabsichtigt sein, wenn man Marks gewissenhaften Ansatz für Tests und DI kennt.

Das wiederum bedeutet, dass mir etwas ziemlich Grundlegendes fehlen muss. Wie kann ich mein SUT von AutoFixture mit einem vorkonfigurierten verspotteten Objekt als Abhängigkeit erstellen lassen? Das Einzige, bei dem ich mir im Moment sicher bin, ist, dass ich das braucheAutoMoqCustomization so muss ich nichts für das konfigurierenITracingService.

AutoFixture / AutoMoq-Pakete sind 2.14.1, Moq ist 3.1.416.3, alle von NuGet. .NET-Version 4.5 (installiert mit VS2012), Verhalten ist in VS2012 und 2010 dasselbe.

Als ich diesen Beitrag schrieb, stellte ich fest, dass einige Leute Probleme mit Moq 4.0 und Assembly Binding Redirects hatten. Deshalb habe ich meine Lösung sorgfältig von allen Instanzen von Moq 4 befreit und Moq 3.1 installiert, indem ich AutoFixture.AutoMoq in "saubere" Projekte installierte. Das Verhalten meines Tests bleibt jedoch unverändert.

Vielen Dank für Hinweise und Erklärungen.

Aktualisieren: Hier ist der Konstruktorcode, nach dem Mark gefragt hat:

public SettingMappingXml(ISettings settingSource, ITracingService tracing)
{
    this._settingSource = settingSource;
    this._tracing = tracing;

    this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}

Und der Vollständigkeit halber dieGetXml() Methode sieht so aus:

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 ist nur eine automatische Eigenschaft.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage