Affectation des paramètres de sortie / référence dans Moq


293

Est-il possible d'assigner un paramètre out/ en refutilisant Moq (3.0+)?

J'ai regardé l'utilisation Callback(), mais Action<>ne prend pas en charge les paramètres ref car il est basé sur des génériques. J'aimerais aussi de préférence mettre une contrainte ( It.Is) sur l'entrée du refparamètre, bien que je puisse le faire dans le rappel.

Je sais que Rhino Mocks prend en charge cette fonctionnalité, mais le projet sur lequel je travaille utilise déjà Moq.


4
Ce Q & A concerne le Moq 3. Le Moq 4.8 offre une prise en charge améliorée des paramètres by-ref, allant d'un correspondeur It.IsAny<T>()similaire ref It.Ref<T>.IsAnyà la prise en charge de la configuration .Callback()et .Returns()via un type de délégué personnalisé correspondant à la signature de la méthode. Les méthodes protégées sont également prises en charge. Voir par exemple ma réponse ci-dessous .
stakx - ne contribue plus

Vous pouvez utiliser out It.Ref <TValue> .Isany pour toute méthode qui utilise out parameter. Par exemple: moq.Setup (x => x.Method (out It.Ref <string> .IsAny) .Returns (TValue);
MikBTC

Réponses:


117

La version 4.8 (ou ultérieure) de Moq offre une prise en charge améliorée des paramètres by-ref:

public interface IGobbler
{
    bool Gobble(ref int amount);
}

delegate void GobbleCallback(ref int amount);     // needed for Callback
delegate bool GobbleReturns(ref int amount);      // needed for Returns

var mock = new Mock<IGobbler>();
mock.Setup(m => m.Gobble(ref It.Ref<int>.IsAny))  // match any value passed by-ref
    .Callback(new GobbleCallback((ref int amount) =>
     {
         if (amount > 0)
         {
             Console.WriteLine("Gobbling...");
             amount -= 1;
         }
     }))
    .Returns(new GobbleReturns((ref int amount) => amount > 0));

int a = 5;
bool gobbleSomeMore = true;
while (gobbleSomeMore)
{
    gobbleSomeMore = mock.Object.Gobble(ref a);
}

Le même modèle fonctionne pour les outparamètres.

It.Ref<T>.IsAnyfonctionne également pour les inparamètres C # 7 (car ils sont également by-ref).


2
c'est la solution, cela vous permet d'avoir n'importe quelle entrée comme ref, exactement comme cela fonctionnerait pour une entrée non ref. C'est vraiment un très bon support amélioré
grathad

5
Cette même solution ne fonctionne pas out, n'est-ce pas?
ATD

1
@ATD en partie oui. Déclarez un délégué avec le paramètre out et affectez la valeur dans le rappel avec la syntaxe ci
royalTS

il convient de mentionner que si la fonction dont vous vous moquez a plus d'arguments, la signature de rappel doit suivre le même modèle (pas seulement le paramètre ref / out)
Yoav Feuerstein

320

Pour 'out', ce qui suit semble fonctionner pour moi.

public interface IService
{
    void DoSomething(out string a);
}

[TestMethod]
public void Test()
{
    var service = new Mock<IService>();
    var expectedValue = "value";
    service.Setup(s => s.DoSomething(out expectedValue));

    string actualValue;
    service.Object.DoSomething(out actualValue);
    Assert.AreEqual(expectedValue, actualValue);
}

J'imagine que Moq regarde la valeur de 'expectValue' lorsque vous appelez le programme d'installation et s'en souvient.

Car ref, je cherche aussi une réponse.

J'ai trouvé le guide de démarrage rapide suivant utile: https://github.com/Moq/moq4/wiki/Quickstart


7
Je pense que le problème que j'ai eu est qu'il n'y a pas de méthode pour assigner les paramètres de sortie / réf de la méthodeSetup
Richard Szalay

1
Je n'ai pas de solution pour assigner un paramètre ref. Cet exemple affecte une valeur de «valeur de sortie» à «b». Moq n'exécute pas l'expression que vous transmettez au programme d'installation, il l'analyse et se rend compte que vous fournissez `` a '' pour une valeur de sortie, il examine donc la valeur actuelle de `` a '' et la mémorise pour les appels suivants.
Craig Celeste

Voir également les exemples de sortie et de référence sur: code.google.com/p/moq/wiki/QuickStart
TrueWill

9
Cela ne fonctionnera pas pour moi lorsque la méthode d'interface Mocked est exécutée dans une portée différente qui a sa propre variable de sortie référencée (par exemple à l'intérieur de la méthode d'une autre classe.) L'exemple donné ci-dessus est pratique car l'exécution se produit dans la même portée que la simulation de configuration, mais il est trop simple de résoudre tous les scénarios. La prise en charge de la gestion explicite de la valeur out / ref est faible dans moq (comme dit par quelqu'un d'autre, géré au moment de l'exécution).
John K

2
+1: c'est une réponse utile. Mais: si le type de paramètre out est une classe plutôt qu'un type intégré comme une chaîne - je ne pense pas que cela fonctionnera. Je l'ai essayé aujourd'hui. L'objet factice simule l'appel et renvoie une valeur nulle via le paramètre "out".
azheglov

86

EDIT : Dans Moq 4.10, vous pouvez maintenant passer un délégué qui a un paramètre out ou ref directement à la fonction de rappel:

mock
  .Setup(x=>x.Method(out d))
  .Callback(myDelegate)
  .Returns(...); 

Vous devrez définir un délégué et l'instancier:

...
.Callback(new MyDelegate((out decimal v)=>v=12m))
...

Pour la version Moq avant 4.10:

Avner Kashtan fournit une méthode d'extension dans son blog qui permet de définir le paramètre de sortie à partir d'un rappel: Moq, Callbacks et Out paramètres: un cas de bord particulièrement délicat

La solution est à la fois élégante et hacky. Élégant en ce qu'il fournit une syntaxe fluide qui se sent chez soi avec d'autres rappels Moq. Et hacky car il repose sur l'appel à certaines API Moq internes via la réflexion.

La méthode d'extension fournie sur le lien ci-dessus n'a pas été compilée pour moi, j'ai donc fourni une version modifiée ci-dessous. Vous devrez créer une signature pour chaque nombre de paramètres d'entrée dont vous disposez; J'ai fourni 0 et 1, mais le prolonger devrait être simple:

public static class MoqExtensions
{
    public delegate void OutAction<TOut>(out TOut outVal);
    public delegate void OutAction<in T1,TOut>(T1 arg1, out TOut outVal);

    public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, TOut>(this ICallback<TMock, TReturn> mock, OutAction<TOut> action)
        where TMock : class
    {
        return OutCallbackInternal(mock, action);
    }

    public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, T1, TOut>(this ICallback<TMock, TReturn> mock, OutAction<T1, TOut> action)
        where TMock : class
    {
        return OutCallbackInternal(mock, action);
    }

    private static IReturnsThrows<TMock, TReturn> OutCallbackInternal<TMock, TReturn>(ICallback<TMock, TReturn> mock, object action)
        where TMock : class
    {
        mock.GetType()
            .Assembly.GetType("Moq.MethodCall")
            .InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
                new[] { action });
        return mock as IReturnsThrows<TMock, TReturn>;
    }
}

Avec la méthode d'extension ci-dessus, vous pouvez tester une interface sans paramètres tels que:

public interface IParser
{
    bool TryParse(string token, out int value);
}

.. avec la configuration Moq suivante:

    [TestMethod]
    public void ParserTest()
    {
        Mock<IParser> parserMock = new Mock<IParser>();

        int outVal;
        parserMock
            .Setup(p => p.TryParse("6", out outVal))
            .OutCallback((string t, out int v) => v = 6)
            .Returns(true);

        int actualValue;
        bool ret = parserMock.Object.TryParse("6", out actualValue);

        Assert.IsTrue(ret);
        Assert.AreEqual(6, actualValue);
    }



Edit : Pour prendre en charge les méthodes de retour vide, vous devez simplement ajouter de nouvelles méthodes de surcharge:

public static ICallbackResult OutCallback<TOut>(this ICallback mock, OutAction<TOut> action)
{
    return OutCallbackInternal(mock, action);
}

public static ICallbackResult OutCallback<T1, TOut>(this ICallback mock, OutAction<T1, TOut> action)
{
    return OutCallbackInternal(mock, action);
}

private static ICallbackResult OutCallbackInternal(ICallback mock, object action)
{
    mock.GetType().Assembly.GetType("Moq.MethodCall")
        .InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock, new[] { action });
    return (ICallbackResult)mock;
}

Cela permet de tester des interfaces telles que:

public interface IValidationRule
{
    void Validate(string input, out string message);
}

[TestMethod]
public void ValidatorTest()
{
    Mock<IValidationRule> validatorMock = new Mock<IValidationRule>();

    string outMessage;
    validatorMock
        .Setup(v => v.Validate("input", out outMessage))
        .OutCallback((string i, out string m) => m  = "success");

    string actualMessage;
    validatorMock.Object.Validate("input", out actualMessage);

    Assert.AreEqual("success", actualMessage);
}

5
@Wilbert, j'ai mis à jour ma réponse avec des surcharges supplémentaires pour les fonctions de retour de vide.
Scott Wegner

2
J'utilise cette solution dans notre suite de tests et je travaille. Cependant, depuis la mise à jour vers Moq 4.10, ce n'est plus le cas.
Ristogod

2
Il semble qu'il ait été cassé dans ce commit github.com/moq/moq4/commit/… . Peut-être y a-t-il une meilleure façon de le faire maintenant?
bougie d'

1
fyi dans Moq4, le MethodCall est maintenant une propriété de la configuration, donc les tripes d'OutCallbackInternal ci-dessus deviennentvar methodCall = mock.GetType().GetProperty("Setup").GetValue(mock); mock.GetType().Assembly.GetType("Moq.MethodCall") .InvokeMember("SetCallbackResponse", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, methodCall, new[] { action });
mike mckechnie

1
@Ristogod, avec la mise à jour vers Moq 4.10, vous pouvez maintenant passer un délégué qui a un paramètre out ou ref directement à la fonction Callback: mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);Vous devrez définir un délégué et l'instancier:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
esteuart

48

Voici la documentation du site Moq :

// out arguments
var outString = "ack";
// TryParse will return true, and the out argument will return "ack", lazy evaluated
mock.Setup(foo => foo.TryParse("ping", out outString)).Returns(true);


// ref arguments
var instance = new Bar();
// Only matches if the ref argument to the invocation is the same instance
mock.Setup(foo => foo.Submit(ref instance)).Returns(true);

5
C'est fondamentalement la même que la réponse de Parched et a la même limitation, en ce qu'elle ne peut pas changer la valeur de sortie en fonction de l'entrée ni répondre aux paramètres ref.
Richard Szalay

@Richard Szalay, vous pourriez, mais vous auriez besoin d'avoir des configurations distinctes avec des paramètres "outString" séparés
Sielu

17

Il semble que ce ne soit pas possible hors de la boîte. On dirait que quelqu'un a tenté une solution

Voir ce post sur le forum http://code.google.com/p/moq/issues/detail?id=176

cette question Vérifier la valeur du paramètre de référence avec Moq


Merçi pour la confirmation. J'avais en fait trouvé ces deux liens dans ma recherche, mais j'ai également remarqué que Moq répertorie l'une de ses fonctionnalités comme "prenant en charge les paramètres ref / out", donc je voulais être sûr.
Richard Szalay

3

Pour renvoyer une valeur avec la définition du paramètre ref, voici un morceau de code:

public static class MoqExtensions
{
    public static IReturnsResult<TMock> DelegateReturns<TMock, TReturn, T>(this IReturnsThrows<TMock, TReturn> mock, T func) where T : class
        where TMock : class
    {
        mock.GetType().Assembly.GetType("Moq.MethodCallReturn`2").MakeGenericType(typeof(TMock), typeof(TReturn))
            .InvokeMember("SetReturnDelegate", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
                new[] { func });
        return (IReturnsResult<TMock>)mock;
    }
}

Déclarez ensuite votre propre délégué correspondant à la signature de la méthode à simuler et fournissez votre propre implémentation de méthode.

public delegate int MyMethodDelegate(int x, ref int y);

    [TestMethod]
    public void TestSomething()
    {
        //Arrange
        var mock = new Mock<ISomeInterface>();
        var y = 0;
        mock.Setup(m => m.MyMethod(It.IsAny<int>(), ref y))
        .DelegateReturns((MyMethodDelegate)((int x, ref int y)=>
         {
            y = 1;
            return 2;
         }));
    }

Est-ce que cela fonctionne lorsque vous n'avez pas accès à la variable qui sera transmise en tant que y? J'ai une fonction qui prend deux arguments ref à DayOfWeek. Je dois définir ces deux éléments à un jour particulier dans le faux moignon et le troisième argument est un contexte de base de données factice. Mais la méthode déléguée n'est tout simplement pas appelée. Il semble que Moq s'attende à ce qu'il corresponde au y local transmis à votre fonction "MyMethod". Est-ce ainsi que cela fonctionne pour votre exemple? THX.
Greg Veres

3

En s'appuyant sur Billy Jakes, j'ai fait une méthode de simulation entièrement dynamique avec un paramètre out. Je poste ceci ici pour quiconque le trouve utile (probablement juste pour moi).

// Define a delegate with the params of the method that returns void.
delegate void methodDelegate(int x, out string output);

// Define a variable to store the return value.
bool returnValue;

// Mock the method: 
// Do all logic in .Callback and store the return value.
// Then return the return value in the .Returns
mockHighlighter.Setup(h => h.SomeMethod(It.IsAny<int>(), out It.Ref<int>.IsAny))
  .Callback(new methodDelegate((int x, out int output) =>
  {
    // do some logic to set the output and return value.
    output = ...
    returnValue = ...
  }))
  .Returns(() => returnValue);

2

Je suis sûr que la solution de Scott a fonctionné à un moment donné,

Mais c'est un bon argument pour ne pas utiliser la réflexion pour jeter un œil aux API privées. C'est cassé maintenant.

J'ai pu définir des paramètres à l'aide d'un délégué

      delegate void MockOutDelegate(string s, out int value);

    public void SomeMethod()
    {
        ....

         int value;
         myMock.Setup(x => x.TryDoSomething(It.IsAny<string>(), out value))
            .Callback(new MockOutDelegate((string s, out int output) => output = userId))
            .Returns(true);
    }

1

Cela peut être une solution.

[Test]
public void TestForOutParameterInMoq()
{
  //Arrange
  _mockParameterManager= new Mock<IParameterManager>();

  Mock<IParameter > mockParameter= new Mock<IParameter >();
  //Parameter affectation should be useless but is not. It's really used by Moq 
  IParameter parameter= mockParameter.Object;

  //Mock method used in UpperParameterManager
  _mockParameterManager.Setup(x => x.OutMethod(out parameter));

  //Act with the real instance
  _UpperParameterManager.UpperOutMethod(out parameter);

  //Assert that method used on the out parameter of inner out method are really called
  mockParameter.Verify(x => x.FunctionCalledInOutMethodAfterInnerOutMethod(),Times.Once());

}

1
C'est fondamentalement la même que la réponse de Parched et a la même limitation, en ce qu'elle ne peut pas changer la valeur de sortie en fonction de l'entrée ni répondre aux paramètres ref.
Richard Szalay

1

J'ai eu du mal avec de nombreuses suggestions ici avant de créer simplement une instance d'une nouvelle classe «Fake» qui implémente l'interface que vous essayez de Mock out. Ensuite, vous pouvez simplement définir la valeur du paramètre out avec la méthode elle-même.


0

J'ai lutté avec cela pendant une heure cet après-midi et je n'ai trouvé aucune réponse nulle part. Après avoir joué seul avec, j'ai pu trouver une solution qui fonctionnait pour moi.

string firstOutParam = "first out parameter string";
string secondOutParam = 100;
mock.SetupAllProperties();
mock.Setup(m=>m.Method(out firstOutParam, out secondOutParam)).Returns(value);

La clé ici est de mock.SetupAllProperties();déterminer toutes les propriétés pour vous. Cela peut ne pas fonctionner dans tous les scénarios de cas de test, mais si tout ce que vous soucier de l'obtenir est return valued' YourMethodalors cela fonctionnera bien.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.