Autofixture: Retravailler l'intégration avec Moq pour prendre en charge les méthodes génériques

Créé le 22 sept. 2019  ·  13Commentaires  ·  Source: AutoFixture/AutoFixture

Cette question est de suivre nos activités concernant la refonte de l'intégration Moq. Actuellement, nous créons Mock<T> aide de la réflexion, puis nous configurons chaque méthode individuellement à l'aide de la réflexion. En raison de cette approche, nous avons une limitation et ne prenons pas en charge les méthodes génériques, car nous ne pouvons pas configurer toutes les instanciations possibles à l'avance.

Nous devrions changer notre approche d'intégration et enregistrer à la place AutoFixture comme fournisseur de valeurs par défaut. De cette façon, nous injectons "passivement" et sommes invoqués par Moq lorsque la valeur est requise. Nous suivons déjà cette voie avec NSubstitute et cela fonctionne plutôt bien.

Nous n'avons pas encore cherché à savoir si c'était possible, mais @stakx a promis d'y jeter un œil un jour.

Reportons cette activité jusqu'au moment où nous commençons notre travail sur la v5, car ce changement pourrait nous obliger à changer la version minimale prise en charge pour Moq. À moins que quelqu'un veuille regarder à l'avance pour préparer Moq à cela.

Je mettrai à jour ce problème une fois que nous aurons commencé à travailler sur la v5.

enhancement

Commentaire le plus utile

    public class ValueProviderTests
    {
        [Fact]
        public void UseValueProvider()
        {
            var fixture = new Fixture();
            var mock = new Mock<IInterfaceWithGenericMethod>
            {
                DefaultValueProvider = new AutoFixtureValueProvider(fixture)
            };


            Assert.NotNull(mock.Object.GenericMethod<string>());
        }

        [Fact]
        public void CustomizedAnonymousType()
        {
            var fixture = new Fixture();
            fixture.Customize<string>(x => x.FromSeed(y => "asf"));
            var mock = new Mock<IInterfaceWithGenericMethod>
            {
                DefaultValueProvider = new AutoFixtureValueProvider(fixture)
            };


            Assert.Equal("asf", mock.Object.GenericMethod<string>());
        }

        public class AutoFixtureValueProvider : DefaultValueProvider
        {
            private readonly IFixture _fixture;

            public AutoFixtureValueProvider(IFixture fixture)
            {
                _fixture = fixture;
            }

            protected override object GetDefaultValue(Type type, Mock mock) =>
                _fixture.Create(type, new SpecimenContext(_fixture));
        }
    }

Cela semble prometteur, à l'exception du fait que la maquette est toujours proche de l'interface. Cependant, je m'attends à ce que la plupart du code de réflexion puisse être remplacé par ceci.

Tous les 13 commentaires

    public class ValueProviderTests
    {
        [Fact]
        public void UseValueProvider()
        {
            var fixture = new Fixture();
            var mock = new Mock<IInterfaceWithGenericMethod>
            {
                DefaultValueProvider = new AutoFixtureValueProvider(fixture)
            };


            Assert.NotNull(mock.Object.GenericMethod<string>());
        }

        [Fact]
        public void CustomizedAnonymousType()
        {
            var fixture = new Fixture();
            fixture.Customize<string>(x => x.FromSeed(y => "asf"));
            var mock = new Mock<IInterfaceWithGenericMethod>
            {
                DefaultValueProvider = new AutoFixtureValueProvider(fixture)
            };


            Assert.Equal("asf", mock.Object.GenericMethod<string>());
        }

        public class AutoFixtureValueProvider : DefaultValueProvider
        {
            private readonly IFixture _fixture;

            public AutoFixtureValueProvider(IFixture fixture)
            {
                _fixture = fixture;
            }

            protected override object GetDefaultValue(Type type, Mock mock) =>
                _fixture.Create(type, new SpecimenContext(_fixture));
        }
    }

Cela semble prometteur, à l'exception du fait que la maquette est toujours proche de l'interface. Cependant, je m'attends à ce que la plupart du code de réflexion puisse être remplacé par ceci.

@riezebosch : LookupOrFallbackDefaultValueProvider au lieu de DefaultValueProvider , vous obtiendrez une prise en charge automatique des valeurs par défaut encapsulées dans Task<> et ValueTask<> terminés. Au cas où vous le voudriez.

@zvirja : Merci pour le rappel !

Quelqu'un a remarqué il y a quelque temps sur le dépôt de Moq qu'en raison de l'accessibilité des méthodes de DefaultValueProvider , ces fournisseurs ne sont pas aussi composables qu'ils pourraient l'être. Je voulais résoudre ce problème dans l'une des prochaines versions mineures. Ce sera probablement un très petit changement décisif. J'ai pensé le mentionner ici afin que nous puissions coordonner les versions / calendriers de publication si nécessaire.

De plus, si quelqu'un ici trouve d'autres problèmes avec DefaultValueProvider Moq, faites-le moi savoir.

Découvrez mon travail sur le fournisseur de valeur : https://github.com/riezebosch/AutoFixture/tree/value-provider. ~ J'ai réécrit un peu les tests qui créent explicitement un Mock<T> partir du projecteur et s'affirment sur .Object ~ Mise à jour : ils sont supprimés car ils ont testé une classe que j'ai supprimée. Je dois vérifier si j'ai perdu un comportement spécifié là-bas.

Ce qui ne fonctionne pas (encore) :

  1. paramètres ref/out, moq ne semble pas visiter le fournisseur de valeur pour cela (@stakx) ?!
  2. ~délégués~
  3. Les méthodes virtuelles sont ignorées (création d'objets uniquement pour les interfaces, les classes abstraites et les délégués)
  4. supprimé les membres configure (pour l'instant).

J'ai dû utiliser la réflexion pour invoquer le SetupAllProperties car il n'est disponible publiquement que sur Mock<T> (@stakx).

Concernant les paramètres ref , ce petit test échoue :

[Fact]
public void RefParameter()
{
    var mock = new Mock<IInterfaceWithRefMethod> { DefaultValueProvider = new TestValueProvider("bye") };
    var s = "hi";

    mock.Object.Method(ref s);

    Assert.Equal("bye", s);
}
[Fact]
public void OutParameter()
{
    var mock = new Mock<IInterfaceWithOutMethod> { DefaultValueProvider = new TestValueProvider(4) };

    mock.Object.Method(out var s);

    Assert.Equal(4, s);
}
private class TestValueProvider : DefaultValueProvider
{
    private readonly object _result;
    public TestValueProvider(object result) => _result = result;
    protected override object GetDefaultValue(Type type, Mock mock) => _result;
}
AutoFixture.AutoMoq.ValueProviderTests.RefParameter

Assert.Equal() Failure
          ↓ (pos 0)
Expected: bye
Actual:   hi
          ↑ (pos 0)AutoFixture.AutoMoq.ValueProviderTests.OutParameter

Assert.Equal() Failure
Expected: 4
Actual:   0

Il semble donc que Moq n'écrase pas la valeur d'un paramètre ref ?

Statut actuel:
Screen Shot 2019-09-24 at 15 01 13

paramètres ref/out, moq ne semble pas visiter le fournisseur de valeur pour cela

Pour autant que je sache, du point de vue de Moq, c'est par conception :

  • ref paramètres

  • out paramètres lus au moment de la configuration pour déterminer la valeur souhaitée à laquelle l'argument doit être défini à la fin de chaque appel de méthode.

C'est pourquoi le fournisseur de valeur par défaut n'est pas interrogé pour ces by-refs.

Si AutoFixture définit toujours les paramètres par référence, alors Moq et AutoFixture ont des modes de fonctionnement différents, et je ne sais pas encore comment nous pouvons combiner les deux de manière transparente.

A dû utiliser la réflexion pour invoquer le SetupAllProperties car il n'est publiquement disponible que sur Mock<T>

Oui, Moq n'a pas d'API publique non générique, c'est dans la planification cependant (voir par exemple https://github.com/moq/moq4/issues/887). Pour le moment, je crains que vous n'ayez besoin de ce détour de réflexion.

paramètres ref/out, moq ne semble pas visiter le fournisseur de valeur pour cela

Pour autant que je sache, du point de vue de Moq, c'est par conception :

  • ref paramètres
  • out paramètres

C'est pourquoi le fournisseur de valeur par défaut n'est pas interrogé pour ces by-refs.

Si AutoFixture définit toujours les paramètres par référence, alors Moq et AutoFixture ont des modes de fonctionnement différents, et je ne sais pas encore comment nous pouvons combiner les deux de manière transparente.

A dû utiliser la réflexion pour invoquer le SetupAllProperties car il n'est publiquement disponible que sur Mock<T>

Oui, Moq n'a pas d'API publique non générique, c'est dans le planning cependant (voir par exemple moq/moq4#887 ) ). Pour le moment, je crains que vous n'ayez besoin de ce détour de réflexion.

Correction mineure, pour le paramètre ref , le fournisseur de valeur _est_ visité mais pour une raison quelconque, la variable n'est pas écrasée (probablement boxing ou quelque chose comme ça ?). Pour le paramètre out , le fournisseur n'est pas du tout visité.

Pour une raison quelconque, les méthodes ref ont été ignorées dans l'implémentation actuelle, donc je suppose que c'est déjà un pas en avant.

@zvirja S'il consultez https://github.com/riezebosch/AutoFixture/tree/value-provider. A fait pas mal de progrès et a pu supprimer beaucoup de code. J'ai fait quelques ajustements pour le faire fonctionner sur mon macbook, l'implémentation réelle et la suppression du code dans des commits séparés.

Laissez-moi savoir ce que vous pensez.

@riezebosch :

Correction mineure, pour le paramètre ref, le fournisseur de valeur est visité mais pour une raison quelconque, la variable n'est pas écrasée (probablement boxing ou quelque chose comme ça ?).

Étrange... rien de ce que vous avez décrit ne devrait arriver. Puis-je vous demander de publier un petit code de démonstration, soit ici, soit (peut-être mieux, afin de ne pas détourner ce problème) sur le dépôt moq/moq4 ?

C'est dans ma réponse ci-dessus : https://github.com/AutoFixture/AutoFixture/issues/1139#issuecomment -534487978

C'est dans ma réponse ci-dessus : #1139 (commentaire)

J'ai exécuté ces tests, qui échouent (comme prévu).

Correction mineure, pour le paramètre ref le fournisseur de valeur est visité

J'ai essayé de reproduire cette affirmation, mais j'ai échoué. J'ai fait la modification suivante :

 private class TestValueProvider : DefaultValueProvider
 {
     private readonly object _result;
     public TestValueProvider(object result) => _result = result;
-    protected override object GetDefaultValue(Type type, Mock mock) => _result;
+    protected override object GetDefaultValue(Type type, Mock mock) => throw new InvalidOperationException("unexpectedly invoked default value provider");
 }

Mais cette exception n'est jamais levée par les deux tests, ce qui signifie que GetDefaultValue n'a pas été invoqué. Votre correction semble donc incorrecte. Qu'est-ce que je rate?

Je suppose que l'exception est avalée par Moq. J'ai légèrement modifié le fournisseur :

[Fact]
public void RefParameter()
{
    var provider = new TestValueProvider("bye");
    var mock = new Mock<IInterfaceWithRefMethod> { DefaultValueProvider = provider };
    var s = "hi";
    mock.Object.Method(ref s);

    Assert.True(provider.IsInvoked);
    Assert.Equal("bye", s);
}
[Fact]
public void OutParameter()
{
    var provider = new TestValueProvider(4);
    var mock = new Mock<IInterfaceWithOutMethod> { DefaultValueProvider = provider };
    mock.Object.Method(out var s);

    Assert.True(provider.IsInvoked);
    Assert.Equal(4, s);
}
private class TestValueProvider : DefaultValueProvider
{
    private readonly object _result;
    public TestValueProvider(object result) => _result = result;
    protected override object GetDefaultValue(Type type, Mock mock)
    {
        IsInvoked = true;
        return _result;
    }
    public bool IsInvoked { get; set; }
}
AutoFixture.AutoMoq.ValueProviderTests.RefParameter

Assert.Equal() Failure
          ↓ (pos 0)
Expected: bye
Actual:   hi
          ↑ (pos 0)
AutoFixture.AutoMoq.ValueProviderTests.OutParameter

Assert.True() Failure
Expected: True
Actual:   False

moq 4.13.0 BTW.

@riezebosch , je ne peux pas reproduire votre résultat de test RefParameter utilisant Moq 4.13.0, le code ci-dessus et les deux interfaces définies ci-dessous. Les deux tests échouent car le fournisseur de valeurs par défaut n'a pas été appelé (comme prévu).

public interface IInterfaceWithRefMethod { void Method(ref string arg); }
public interface IInterfaceWithOutMethod { void Method(out int    arg); }

(Peut-être que votre méthode IInterfaceWithRefMethod.Method a un type de retour non void ? Cela entraînerait évidemment l'appel du fournisseur de valeur par défaut, puis votre test rejette la valeur de retour qu'il produit.)

Vous avez raison, le fournisseur de valeur est visité pour la méthode de paramètre ref mais uniquement parce que la méthode d'interface est définie comme string Method(ref string s); .

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

Questions connexes

ploeh picture ploeh  ·  7Commentaires

ecampidoglio picture ecampidoglio  ·  7Commentaires

Eldar1205 picture Eldar1205  ·  5Commentaires

josh-degraw picture josh-degraw  ·  4Commentaires

zvirja picture zvirja  ·  4Commentaires