Autofixture: AutoMoq une dépendance concrète

Créé le 1 oct. 2020  ·  15Commentaires  ·  Source: AutoFixture/AutoFixture

Salut,

J'essaie d'utiliser un Moq d'une dépendance concrète.

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

Cela renvoie une erreur où AutoFixture essaie de créer une instance réelle de IdentityServerOptions à injecter dans mon sut au lieu d'utiliser la maquette gelée.
Si je supprime le sut et crée manuellement le passage dans le identityServerOptions.Object alors tout fonctionne, donc ma théorie de travail est que l'autofixture/automoq ne détecte pas que le moq gelé peut satisfaire la dépendance et essaie donc de créer un nouveau exemple.

J'ai également essayé Frozen(Matching.DirectBaseType) car le proxy généré par moq hérite de la valeur demandée, mais toujours pas de chance.

Y a-t-il un moyen de contourner cela car je ne peux pas changer IdentityServerOptions car il s'agit d'un tiers et j'ai plus de variables à injecter dans mon sut, ce qui rend la solution de contournement un peu compliquée lorsque je n'ai besoin d'accéder qu'à celui de mon exemple .

Merci
Euan

question

Commentaire le plus utile

J'ai enfin eu l'occasion de le tester et il fonctionne très bien.
J'ai apporté une amélioration mineure (je sais que ce n'est toujours pas assez robuste pour toutes les utilisations) afin que vous puissiez toujours demander l'objet concret au lieu du Mock wrapper.

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

Permet

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

Tous les 15 commentaires

Il suffit de congeler IdentityServerOptions et d'utiliser Mock.Get .

Consultez cette page du livret que j'ai écrit pour mes développeurs : https://docs.educationsmediagroup.com/unit-testing-csharp/autofixture/combining-autofixture-with-nunit-and-moq

Malheureusement, cela ne fonctionne pas car IdentityServerOptions n'est pas une classe abstraite ni une interface et donc le AutoMoqCustomization ne génère pas de Moq pour cela.

Pour être complet, l'attribut est :

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

Notez que pour les interfaces et les classes abstraites, il ne semble pas important que vous figiez le wrapper fictif ou l'interface, puis que vous récupériez le simulacre ou que vous utilisiez .Object dans votre cas de test.

Je pense qu'il est tout à fait correct qu'une classe concrète dans les paramètres vous donne une copie réelle, sinon votre sut ne fonctionnerait pas, mais si vous spécifiez une dépendance avec le wrapper Mock pour forcer un simulacre alors Mock<IInterface> fonctionne comme on peut s'y attendre, mais pas Mock<Concrete> .

Ensuite, je ne comprends pas votre problème. Pourquoi avez-vous besoin d'utiliser Moq ?

IdentityServerOptions n'est pas ma classe et ne peut pas être instancié facilement à partir d'un test unitaire, je dois donc le simuler pour le transmettre à mon sut . La moquerie fonctionne très bien, mais il semble qu'AutoFixture/AutoMoq ne détecte pas le fait qu'un Mockpeut être utilisé à la place d'une dépendance IdentityServerOptions .

Solution de contournement actuelle :

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

Cela rend un peu compliqué d'avoir à répertorier toutes les dépendances dans chaque test alors que je n'ai généralement besoin que de la maquette IdentityServerOptions pour la plupart des cas de test.

@Euan-McVie désolé pour une réponse tardive.
J'espère faire la lumière sur le problème que vous rencontrez et éventuellement une solution de contournement qui pourrait vous aider.

Bien que je n'étais pas là quand AutoMoq a été développé, je pense que ce comportement était voulu.

Comme vous le savez probablement, AutoMoq peut injecter des mocks de deux manières, soit en tant que mock, soit en tant que type mocked.
L'implémentation actuelle se moque par défaut de toute interface ou type abstrait, étant donné qu'aucune autre personnalisation n'a déjà intercepté la demande pour le même type. C'est pourquoi le mocking automatique se produit pour les demandes qui ont traversé tout le pipeline de résolution, juste avant de lever une exception car la demande n'a pas pu être satisfaite.

Si nous devions déplacer les personnalisations d'auto-mocking plus près du début du pipeline, alors la plupart des types seraient résolus comme des simulations. Ce n'est pas souhaitable car alors les dépendances de types seraient null ou le simulacre serait lancé lors de la création car certaines clauses de garde empêchaient la création de l'objet dans un état invalide.

Maintenant, il peut sembler logique que les [Frozen] soient correctement résolues, mais en raison de la conception interne, nous ne pouvons pas dire quels types sont gelés/injectés car les conditions dans lesquelles la même instance serait toujours renvoyée sont trop complexes.
Une demande de contexte pour une simulation d'un type simulé produirait presque toujours une valeur, car presque tous les types peuvent être simulés et nous ne saurions pas si la demande a été satisfaite par une valeur gelée.

Pour contourner votre problème, je suggère de personnaliser le type concret à créer à partir d'une maquette.

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

Étant donné que vous auriez déjà gelé la maquette dans votre paramètre de test lorsque vous l'aviez demandé dans votre SUT, vous recevriez la même instance.

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

Je vais laisser ce sujet ouvert pendant un petit moment, au cas où vous auriez d'autres questions sur ce sujet.

Merci pour la réponse.

Est-ce que cela fonctionnerait si je créais un MockAttribute : CustomizeAttribute pour que je puisse faire quelque chose comme :

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

Cela serait préférable car alors l'attribut standard AutoMoqData fonctionne pour générer les autres paramètres afin que je puisse contrôler les simulations spécifiques d'objets concrets par test plutôt que d'avoir beaucoup d'attributs de style AutoMoqWithMockXYZ pour d'autres scénarios.

Cela fonctionnerait-il (pas encore eu la chance de tester moi-même), ou ne se combinerait-il pas correctement avec le Frozen et le sous-jacent AutoMoqData ?

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

Merci

@Euan-McVie Je suppose que vous pourriez effectivement le faire. Voici une implémentation naïve d'une personnalisation qui pourrait permettre de se moquer de paramètres spécifiques

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

Vous pouvez ensuite définir un attribut qui l'utilise

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

et finalement le test ressemblerait à quelque chose comme ça

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

Clôture du problème.
La question semble avoir trouvé une réponse.

Ça a l'air super merci.

@aivascu pensez -vous que les types ci-dessus pourraient être incorporés dans la bibliothèque AutoMoq ?

Ils semblent assez génériques et à usage général pour mériter cet honneur.

@Kralizek oui, il pourrait être utile de l'avoir dans la bibliothèque AutoMoq.
Je vais ajouter un problème au backlog, avec une proposition pour cette fonctionnalité.

J'ai enfin eu l'occasion de le tester et il fonctionne très bien.
J'ai apporté une amélioration mineure (je sais que ce n'est toujours pas assez robuste pour toutes les utilisations) afin que vous puissiez toujours demander l'objet concret au lieu du Mock wrapper.

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

Permet

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

@Kralizek oui, il pourrait être utile de l'avoir dans la bibliothèque AutoMoq.
Je vais ajouter un problème au backlog, avec une proposition pour cette fonctionnalité.

Puisque je suis récemment tombé sur ce même problème, cela vous dérange-t-il que je propose un PR pour implémenter le MockAttribute comme vous l'avez spécifié ?

@aivascu

@Kralizek oui, veuillez le faire si vous l'avez implémenté. AFAIR lorsque j'ai tenté de l'implémenter, ce n'était pas aussi trivial que l'implémentation d'en haut. Déplaçons la discussion sur #1269 .

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

ploeh picture ploeh  ·  7Commentaires

Ephasme picture Ephasme  ·  3Commentaires

zvirja picture zvirja  ·  4Commentaires

ploeh picture ploeh  ·  3Commentaires

Accc99 picture Accc99  ·  4Commentaires