web-dev-qa-db-fra.com

Assignation des paramètres out / ref dans Moq

Est-il possible d'assigner un paramètre out/ref à l'aide de Moq (3.0+)?

J'ai déjà envisagé d'utiliser 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 paramètre ref, 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.

254
Richard Szalay

Bien que la question concerne Moq 3 (probablement en raison de son âge), permettez-moi d’afficher une solution pour Moq 4.8, qui a considérablement amélioré la prise en charge des paramètres de référence.

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

A propos: It.Ref<T>.IsAny fonctionne également pour les paramètres C # 7 in (car ils sont également by-ref).

76
stakx

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 considère la valeur de "attendueValeur" lorsque vous appelez le programme d'installation et s'en souvient.

Pour 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

296
Craig Celeste

EDIT: Dans Moq 4.10, vous pouvez maintenant passer un délégué disposant d'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 les versions de Moq antérieures à 4.10:

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

La solution est à la fois élégante et sophistiquée. Élégant, il fournit une syntaxe fluide qui se sent à l'aise avec les autres callbacks de Moq. Et hacky, car il repose sur l’appel de certaines API Moq internes par 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 que vous avez; 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 avec des paramètres tels que:

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

.. avec la configuration suivante de Moq:

    [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 de 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);
}
80
Scott Wegner

Ceci est la documentation de 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);
47
Kosau

On dirait qu'il n'est pas possible de sortir de la boîte. On dirait que quelqu'un a tenté une solution

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

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

17
Gishu

Pour renvoyer une valeur avec le paramètre setting 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;
    }
}

Ensuite, déclarez votre propre délégué correspondant à la signature de la méthode to-be-mocked 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;
         }));
    }
2
Victor Mukherjee

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
Fabrice

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

1
Casey O'Brien

J'ai eu du mal avec cela pendant une heure cet après-midi et je ne pouvais trouver de réponse nulle part. Après avoir joué seul avec elle, 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 mock.SetupAllProperties(); qui détruira toutes les propriétés pour vous. Cela peut ne pas fonctionner dans tous les scénarios de test, mais si tout ce qui vous importe est d'obtenir le return value de YourMethod, cela fonctionnera sans problème.

0
maxshuty