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
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 MockIdentityServerOptions
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 .
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.
Erlaubt