Autofixture: AutoMoq eine konkrete Abhängigkeit

Erstellt am 1. Okt. 2020  ·  15Kommentare  ·  Quelle: AutoFixture/AutoFixture

Hi,

Ich versuche, ein Moq einer konkreten Abhängigkeit zu verwenden.

[Theory, AutoMoqData]
public async Task Can_register_a_single_service_successfully(
    [Frozen] Mock<IdentityServerOptions> identityServerOptions,
    ServiceDiscoveryUsingIdentityServer sut
)
{...}

Dies löst einen Fehler aus, bei dem AutoFixture versucht, eine echte Instanz von IdentityServerOptions zu erstellen, um sie in mein sut zu injizieren, anstatt den eingefrorenen Mock zu verwenden.
Wenn ich die Sut entferne und manuell die Übergabe von identityServerOptions.Object erstelle, dann funktioniert alles, also ist meine Arbeitstheorie, dass Autofixture/Automoq nicht erkennt, dass das eingefrorene Moq die Abhängigkeit erfüllen kann und versucht daher, ein neues zu erstellen Beispiel.

Ich habe auch Frozen(Matching.DirectBaseType) ausprobiert, da der von moq generierte Proxy vom angeforderten Wert erbt, aber immer noch kein Glück.

Gibt es einen Weg, dies zu umgehen, da ich IdentityServerOptions nicht ändern kann, da es sich um einen Drittanbieter handelt und mehr Variablen in meine sut einschleusen, was die Problemumgehung etwas chaotisch macht, wenn ich nur auf die aus meinem Beispiel zugreifen muss? .

Vielen Dank
Euan

question

Hilfreichster Kommentar

Hatte endlich die Gelegenheit es zu testen und es funktioniert super.
Ich habe eine kleine Verbesserung vorgenommen (ich weiß, dass es immer noch nicht robust genug für alle Anwendungen ist), sodass Sie immer noch das konkrete Objekt anstelle des Mock-Wrappers anfordern können.

public void Customize(IFixture fixture)
{
    Type? mockedType = (typeof(IMock<object>).IsAssignableFrom(ParameterInfo.ParameterType))
        ? ParameterInfo.ParameterType.GetGenericArguments()[0]
        : ParameterInfo.ParameterType;
    fixture.Customizations.Add(new MockRelay(new ExactTypeSpecification(mockedType)));
}

Erlaubt

[Theory]
[AutoData]
public void DependencyShouldBeSameAsMockedObject(
    [Frozen][Mock] DependencyClass dependencyMock,
    SystemUnderTest sut)
{
    Assert.Same(dependencyMock, sut.Dependency);
}

Alle 15 Kommentare

Frieren Sie einfach IdentityServerOptions und verwenden Sie Mock.Get .

Sehen Sie sich diese Seite der Broschüre an, die ich für meine Entwickler geschrieben habe: https://docs.educationsmediagroup.com/unit-testing-csharp/autofixture/combining-autofixture-with-nunit-and-moq

Leider funktioniert das nicht, da IdentityServerOptions weder eine abstrakte Klasse noch ein Interface ist und AutoMoqCustomization kein Moq dafür generiert.

Der Vollständigkeit halber lautet das Attribut:

public AutoMoqDataAttribute()
    : base(() => new Fixture().Customize(new AutoMoqCustomization()))
{ }

Beachten Sie, dass es für Interfaces und abstrakte Klassen keine Rolle zu spielen scheint, ob Sie den Mock-Wrapper oder das Interface einfrieren und dann entweder den Mock abrufen oder .Object in Ihrem Testfall verwenden.

Ich glaube, es ist völlig richtig, dass eine konkrete Klasse in den Parametern Ihnen eine echte Kopie geben sollte, sonst würde Ihr sut nicht funktionieren, aber wenn Sie eine Abhängigkeit mit dem Mock-Wrapper angeben, um einen Mock zu erzwingen, dann Mock<IInterface> funktioniert wie erwartet, Mock<Concrete> jedoch nicht.

Dann verstehe ich dein Problem nicht. Warum müssen Sie Moq überhaupt verwenden?

IdentityServerOptions ist nicht meine Klasse und kann nicht einfach aus einem Komponententest instanziiert werden, daher muss ich sie nachahmen, um in meine sut . Das Verspotten funktioniert alles großartig, aber es scheint, dass AutoFixture/AutoMoq die Tatsache nicht aufgreift, dass ein Mockkann anstelle einer IdentityServerOptions Abhängigkeit verwendet werden.

Aktueller Workaround:

[Theory, AutoMoqData]
public async Task Can_register_a_single_service_successfully(
    [Frozen] ILogger<ServiceDiscoveryUsingIdentityServer> logger,
    [Frozen] IAuditor<ServiceDiscoveryUsingIdentityServer> auditor,
    [Frozen] Mock<IdentityServerOptions> identityServerOptions,
    ... the other dependencies.
)
{
   //Arrange
   var sut= new ServiceDiscoveryUsingIdentityServer(logger, auditor, identityServerOptions, identityService, authorizationService, grpcService);
}

Dies macht es ein wenig chaotisch, alle Abhängigkeiten in jedem Test auflisten zu müssen, wenn ich normalerweise nur den IdentitätsserverOptions-Mock für die meisten Testfälle benötige.

@Euan-McVie Entschuldigung für die späte Antwort.
Ich hoffe, dass ich etwas Licht in das von Ihnen aufgetretene Problem und möglicherweise eine Problemumgehung werfen kann, die Ihnen helfen könnte.

Obwohl ich nicht dabei war, als AutoMoq entwickelt wurde, glaube ich, dass dieses Verhalten beabsichtigt war.

Wie Sie wahrscheinlich wissen, kann AutoMoq Mocks auf zwei Arten injizieren, entweder als Mock oder als Mock.
Die aktuelle Implementierung verspottet standardmäßig jede Schnittstelle oder jeden abstrakten Typ, da keine anderen Anpassungen die Anforderung für denselben Typ bereits abgefangen haben. Aus diesem Grund erfolgt das automatische Mocking für Anfragen, die die gesamte Auflösungspipeline durchlaufen haben, kurz bevor eine Ausnahme ausgelöst wird, da die Anfrage nicht erfüllt werden konnte.

Wenn wir die Auto-Mocking-Anpassungen näher an den Anfang der Pipeline verschieben würden, würden die meisten Typen als Mocks aufgelöst. Dies ist nicht wünschenswert, da dann die Typenabhängigkeiten null wären oder der Mock bei der Erstellung ausgelöst würde, da einige Schutzklauseln die Erstellung des Objekts in einem ungültigen Zustand verhinderten.

Nun mag es logisch erscheinen, dass die [Frozen] Mocks richtig aufgelöst werden, aber aufgrund des internen Designs können wir nicht sagen, welche Typen eingefroren/injiziert werden, da die Bedingungen, unter denen immer dieselbe Instanz zurückgegeben würde, zu komplex sind.
Eine Kontextanfrage für einen Mock eines verspotteten Typs würde fast immer einen Wert liefern, da fast jeder Typ verspottet werden kann und wir nicht wissen, ob die Anfrage durch einen eingefrorenen Wert erfüllt wurde.

Als Workaround für Ihr Problem schlage ich vor, den konkreten Typ anzupassen, der aus einem Mock erstellt werden soll.

public class CustomCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<IdentityServerOptions>(
            x => x.FromFactory<IFixture>(
                f => f.Create<Mock<IdentityServerOptions>>().Object));
    }
}

Da Sie den Mock bereits in Ihrem Testparameter eingefroren hätten, als Sie ihn in Ihrem SUT angefordert hatten, würden Sie dieselbe Instanz erhalten.

[Theory, AutoMoqData]
public async Task Can_register_a_single_service_successfully(
    [Frozen] Mock<IdentityServerOptions> identityServerOptions,
    ServiceDiscoveryUsingIdentityServer sut)

Ich werde dieses Thema noch eine Weile offen lassen, falls Sie weitere Fragen zu diesem Thema haben.

Danke für die Antwort.

Würde es funktionieren, wenn ich ein benutzerdefiniertes MockAttribute : CustomizeAttribute erstellen würde, damit ich Folgendes tun könnte:

[Theory, AutoMoqData]
public async Task Can_register_a_single_service_successfully(
    [Frozen, Mock(typeof(IdentityServerOptions))] IdentityServerOptions identityServerOptions,
    [Frozen] ILogger<ServiceDiscoveryUsingIdentityServer> logger,
    [Frozen] IAuditor<ServiceDiscoveryUsingIdentityServer> auditor,
    ServiceDiscoveryUsingIdentityServer sut)

Dies wäre vorzuziehen, da dann das Standardattribut AutoMoqData funktioniert, um die anderen Parameter zu generieren, sodass ich die spezifischen Nachbildungen konkreter Objekte pro Test steuern kann, anstatt viele AutoMoqWithMockXYZ Stilattribute für andere Szenarien zu haben.

Würde das möglicherweise funktionieren (hatte selbst noch keine Gelegenheit zum Testen), oder würde es nicht richtig mit den Frozen und den zugrunde liegenden AutoMoqData kombiniert werden?

public AutoMoqDataAttribute()
    : base(() => new Fixture().Customize(new AutoMoqCustomization()))
{ }

Vielen Dank

@Euan-McVie Ich nehme an, Sie könnten das tatsächlich tun. Hier ist eine naive Implementierung einer Anpassung, die eine Parameter-spezifische Verspottung erreichen könnte

public class MockCustomization : ICustomization
{
    public MockCustomization(ParameterInfo parameterInfo)
    {
        ParameterInfo = parameterInfo;
    }

    public ParameterInfo ParameterInfo { get; }

    public void Customize(IFixture fixture)
    {
        var mockedType = ParameterInfo.ParameterType.GetGenericArguments()[0];
        fixture.Customizations.Add(new MockRelay(new ExactTypeSpecification(mockedType)));
    }
}

Sie könnten dann ein Attribut definieren, das es verwendet

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class MockAttribute : Attribute, IParameterCustomizationSource
{
    public ICustomization GetCustomization(ParameterInfo parameter)
    {
        return new MockCustomization(parameter);
    }
}

und zum Schluss würde der Test ungefähr so ​​aussehen

[Theory]
[AutoData]
public void DependencyShouldBeSameAsMockedObject(
    [Frozen][Mock] Mock<DependencyClass> dependencyMock,
    SystemUnderTest sut)
{
    Assert.Same(dependencyMock.Object, sut.Dependency);
}

Schließen des Themas.
Die Frage scheint beantwortet zu sein.

Sieht super aus danke.

@aivascu denken Sie, dass die oben genannten Typen in die AutoMoq-Bibliothek aufgenommen werden könnten?

Sie scheinen generisch und allgemein genug zu sein, um die Ehre zu verdienen.

@Kralizek ja, es könnte nützlich sein, es in der AutoMoq-Bibliothek zu haben.
Ich werde dem Backlog ein Problem mit einem Vorschlag für diese Funktion hinzufügen.

Hatte endlich die Gelegenheit es zu testen und es funktioniert super.
Ich habe eine kleine Verbesserung vorgenommen (ich weiß, dass es immer noch nicht robust genug für alle Anwendungen ist), sodass Sie immer noch das konkrete Objekt anstelle des Mock-Wrappers anfordern können.

public void Customize(IFixture fixture)
{
    Type? mockedType = (typeof(IMock<object>).IsAssignableFrom(ParameterInfo.ParameterType))
        ? ParameterInfo.ParameterType.GetGenericArguments()[0]
        : ParameterInfo.ParameterType;
    fixture.Customizations.Add(new MockRelay(new ExactTypeSpecification(mockedType)));
}

Erlaubt

[Theory]
[AutoData]
public void DependencyShouldBeSameAsMockedObject(
    [Frozen][Mock] DependencyClass dependencyMock,
    SystemUnderTest sut)
{
    Assert.Same(dependencyMock, sut.Dependency);
}

@Kralizek ja, es könnte nützlich sein, es in der AutoMoq-Bibliothek zu haben.
Ich werde dem Backlog ein Problem mit einem Vorschlag für diese Funktion hinzufügen.

Da ich kürzlich über genau dieses Problem gestolpert bin, würde es Ihnen etwas ausmachen, dass ich eine PR vorlege, um das MockAttribute wie von Ihnen angegeben umzusetzen?

@aivascu

@Kralizek ja, bitte, wenn Sie es implementiert haben. Als ich versuchte, AFAIR zu implementieren, war es nicht so trivial wie die Implementierung von oben. Verschieben wir die Diskussion zu #1269 .

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen