Atribuindo parâmetros out / ref no Moq

293

É possível atribuir um out/ refparâmetro usando Moq (3.0+)?

Eu olhei para usar Callback(), mas Action<>não suporta parâmetros de ref porque é baseado em genéricos. Também gostaria preferencialmente de colocar uma restrição ( It.Is) na entrada do refparâmetro, embora eu possa fazer isso no retorno de chamada.

Eu sei que o Rhino Mocks suporta essa funcionalidade, mas o projeto no qual estou trabalhando já está usando o Moq.

Richard Szalay
fonte
4
As perguntas e respostas são sobre o Moq 3. O Moq 4.8 possui um suporte aprimorado para parâmetros by-ref, variando de um It.IsAny<T>()matcher-like ( ref It.Ref<T>.IsAny) a suporte para configuração .Callback()e .Returns()por meio de tipos de delegados personalizados que correspondem à assinatura do método. Métodos protegidos são igualmente suportados. Veja, por exemplo, minha resposta abaixo .
stakx - não está mais contribuindo com
Você pode usar It.Ref <TValue> .Isany para qualquer método que utilize o parâmetro Por exemplo: moq.Setup (x => x.Method (sai It.Ref <.> IsAny) .Returns (TValue);
MikBTC

Respostas:

117

O Moq versão 4.8 (ou posterior) aprimorou muito o suporte a parâmetros 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);
}

O mesmo padrão funciona para outparâmetros.

It.Ref<T>.IsAny também funciona para C # 7 in parâmetros (já que eles também são por-ref).

stakx - não está mais contribuindo
fonte
2
esta é a solução, isso permite que você tenha qualquer entrada como ref, exatamente como funcionaria para entradas não ref. Este é realmente um suporte aprimorado muito bom
grathad
5
Essa mesma solução não funciona out, porém, funciona?
ATD
1
@ ATD parcialmente sim. Declarar um delegado com o parâmetro de saída e atribuir o valor no retorno de chamada com a sintaxe de cima
RoyalTS
vale ressaltar que se a função que você está zombando tiver mais argumentos, a assinatura de retorno de chamada deve seguir o mesmo padrão (não apenas o parâmetro ref / out)
Yoav Feuerstein
320

Para 'fora', o seguinte parece funcionar para mim.

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

Eu estou supondo que o Moq analisa o valor de'pectedValue 'quando você chama o programa de instalação e se lembra dele.

Para ref também estou procurando uma resposta.

Achei o seguinte guia de início rápido útil: https://github.com/Moq/moq4/wiki/Quickstart

Craig Celeste
fonte
7
Eu acho que o problema que tive foi que, onde há um método de atribuir a out / params ref do métodoSetup
Richard Szalay
1
Não tenho uma solução para atribuir um parâmetro ref. Este exemplo atribui um valor de "valor de saída" a 'b'. O Moq não executa a Expressão que você passa para a Instalação, analisa-a e percebe que você está fornecendo 'a' para um valor de saída; portanto, ele analisa o valor atual de 'a' e o lembra das chamadas subseqüentes.
Craig Celeste
Veja também os exemplos out e ref em: code.google.com/p/moq/wiki/QuickStart
TrueWill
9
Isso não funcionará para mim quando o método de interface Mocked for executado em um escopo diferente que possua sua própria variável de saída referenciada (por exemplo, dentro do método de outra classe). O exemplo fornecido acima é conveniente porque a execução ocorre no mesmo escopo que a configuração simulada, no entanto, é muito simples resolver todos os cenários. O suporte para manipulação explícita do valor out / ref é fraco em moq (como dito por outra pessoa, manipulado no tempo de execução).
John K
2
+1: esta é uma resposta útil. Mas: se o tipo de parâmetro out for uma classe e não um tipo incorporado, como string - não acredito que isso funcione. Tentei hoje. O objeto simulado simula a chamada e retorna um nulo através do parâmetro "out".
Azheglov
86

EDIT : No Moq 4.10, agora você pode passar um delegado que possui um parâmetro out ou ref diretamente para a função de retorno de chamada:

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

Você precisará definir um delegado e instancia-lo:

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

Para a versão Moq anterior à 4.10:

Avner Kashtan fornece um método de extensão em seu blog que permite definir o parâmetro out de um retorno de chamada: parâmetros Moq, Callbacks e Out: um caso particularmente delicado

A solução é elegante e hacky. Elegante, pois fornece uma sintaxe fluente que se sente em casa com outros retornos de chamada do Moq. E hacky porque depende de chamar algumas APIs Moq internas por meio de reflexão.

O método de extensão fornecido no link acima não foi compilado para mim, por isso forneci uma versão editada abaixo. Você precisará criar uma assinatura para cada número de parâmetros de entrada que você possui; Forneci 0 e 1, mas estendê-lo ainda mais deve ser simples:

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

Com o método de extensão acima, você pode testar uma interface sem parâmetros como:

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

.. com a seguinte configuração do 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);
    }



Editar : Para oferecer suporte a métodos de retorno nulo, basta adicionar novos métodos de sobrecarga:

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

Isso permite testar interfaces como:

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);
}
Scott Wegner
fonte
5
@ Wilbert, atualizei minha resposta com sobrecargas adicionais para funções de retorno de vazio.
21413 Scott Wegner
2
Eu tenho usado esta solução em nossa suíte de testes e estava trabalhando. No entanto, desde a atualização para o Moq 4.10, isso não acontece mais.
Ristogod 13/09/18
2
Parece que ele foi quebrado neste commit github.com/moq/moq4/commit/… . Talvez haja uma maneira melhor de fazer isso agora?
sparkplug
1
fyi em Moq4 o MethodCall é agora uma propriedade da configuração, de modo a coragem de OutCallbackInternal acima muda paravar 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, com a atualização para o Moq 4.10, agora você pode passar um delegado que possui um parâmetro out ou ref diretamente para a função de retorno de mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);...Callback(new MyDelegate((out decimal v)=>v=12m))...;
chamada
48

Esta é a documentação do 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);
Kosau
fonte
5
Isso é basicamente o mesmo que a resposta de Parched e tem a mesma limitação, pois ele não pode alterar o valor de saída dependendo da entrada, nem pode responder aos parâmetros ref.
Richard Szalay
@ Richard Szalay, Você pode, mas você precisa ter configurações separadas com parâmetros separados "outstring"
Sielu
3

Para retornar um valor junto com a definição do parâmetro ref, aqui está um pedaço de código:

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

Em seguida, declare seu próprio representante que corresponde à assinatura do método a ser ridicularizado e forneça sua própria implementação de método.

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;
         }));
    }
Victor Mukherjee
fonte
Isso funciona quando você não tem acesso à variável que será passada como y? Eu tenho uma função que leva dois argumentos ref para DayOfWeek. Preciso definir ambos para um dia específico no stub de simulação e o terceiro argumento é um contexto de banco de dados de simulação. Mas o método delegado simplesmente não está sendo chamado. Parece que o Moq esperava corresponder ao local que está sendo passado pela sua função "MyMethod". É assim que funciona no seu exemplo? THX.
Greg Veres
3

Com base em Billy Jakes awnser, criei um método de simulação totalmente dinâmico com um parâmetro out. Estou postando isso aqui para quem achar útil (provavelmente apenas o futuro para mim).

// 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);
Martijn
fonte
2

Tenho certeza que a solução de Scott funcionou em um ponto,

Mas é um bom argumento para não usar a reflexão para espiar as APIs privadas. Está quebrado agora.

Consegui definir parâmetros usando um delegado

      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);
    }
Billy Jake O'Connor
fonte
1

Isso pode ser uma solução.

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

}
Fabrice
fonte
1
Isso é basicamente o mesmo que a resposta de Parched e tem a mesma limitação, pois ele não pode alterar o valor de saída dependendo da entrada, nem pode responder aos parâmetros ref.
Richard Szalay
1

Lutei com muitas das sugestões aqui antes de criar uma instância de uma nova classe 'Fake' que implementa qualquer interface que você esteja tentando zombar. Em seguida, você pode simplesmente definir o valor do parâmetro out com o próprio método.

Casey O'Brien
fonte
0

Eu lutei com isso por uma hora esta tarde e não consegui encontrar resposta em lugar algum. Depois de brincar sozinho, consegui encontrar uma solução que funcionasse para mim.

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

A chave aqui é mock.SetupAllProperties();que irá remover todas as propriedades para você. Isso pode não funcionar em todos os cenários caso de teste, mas se tudo o que importa é conseguir o return valuede YourMethodseguida, este trabalho bem.

maxshuty
fonte