Призначення параметрів виводу / перегляду в Moq


293

Чи можливо призначити out/ refпараметр за допомогою Moq (3.0+)?

Я розглядав використання Callback(), але Action<>не підтримує параметри ref, тому що він заснований на генериці. Я також хотів би встановити обмеження ( It.Is) на вхід refпараметра, хоча я можу це зробити у зворотному дзвінку.

Я знаю, що Rhino Mocks підтримує цю функціональність, але проект, над яким я працюю, вже використовує Moq.


4
Ця запитання стосується Moq 3. Moq 4.8 має значно вдосконалену підтримку параметрів It.IsAny<T>()побічної відміни , починаючи від аналогічного матчера ( ref It.Ref<T>.IsAny) до підтримки для налаштування .Callback()та .Returns()через користувацькі типи делегата, що відповідають підпису методу. Захищені методи однаково підтримуються. Дивіться, наприклад, мою відповідь нижче .
stakx - більше не вносить внесок

Ви можете використовувати It.Ref <TValue> .Isany для будь-якого методу, який використовує параметр. Наприклад: moq.Setup (x => x.Method (вихід It.Ref <string> .IsAny). Повернення (TValue);
MikBTC

Відповіді:


118

Moq версії 4.8 (або пізнішої) має значно вдосконалену підтримку параметрів додаткової відмови:

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

Ця ж схема працює для outпараметрів.

It.Ref<T>.IsAnyтакож працює для inпараметрів C # 7 (оскільки вони також є побічними).


2
це рішення, це дозволить вам мати будь-який вклад як ref, точно так, як він би працював для не введення посилання. Це дуже приємна покращена підтримка
благодарив

5
Це ж рішення не працює, outхоча?
ATD

1
@ATD частково так. Оголосити делегата параметром out та призначити значення зворотного дзвінка з синтаксисом зверху
royalTS

Варто згадати, що якщо функція, яку ви знущаєтесь, має більше аргументів, тоді підпис зворотного дзвінка повинен відповідати тій же схемі (не лише параметр відхилення / вимкнення)
Yoav Feuerstein

320

Для «поза», здається, для мене працює наступне.

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

Я здогадуюсь, що Moq дивиться на значення "очікувана валя", коли ви викликаєте програму Setup і запам'ятовує це.

Бо refя теж шукаю відповідь.

Я вважав корисним наступний посібник QuickStart: https://github.com/Moq/moq4/wiki/Quickstart


7
Я думаю, що проблема в мене полягала в тому, що немає способу присвоїти параметри виходу / перегляду методуSetup
Річард Салай

1
У мене немає рішення для призначення параметра ref. Цей приклад присвоює значення "вихідного значення" на "b". Moq не виконує вираз, який ви передаєте Установці, він аналізує його і розуміє, що ви надаєте "a" для вихідного значення, тому він переглядає теперішнє значення "a" і запам'ятовує його для наступних дзвінків.
Крейг Селесте

Дивіться також приклади виходу та посилання на: code.google.com/p/moq/wiki/QuickStart
TrueWill

9
Це не спрацює для мене, коли метод Mocked інтерфейс виконується в іншій області, яка має власну посилальну вихідну змінну (наприклад, всередині методу іншого класу.) Приклад, наведений вище, є зручним, оскільки виконання відбувається в тій же області, що і налаштування макету, проте вирішити всі сценарії занадто просто. Підтримка явного поводження зі значенням out / ref слабка в moq (за словами когось іншого, оброблюваного під час виконання).
Джон К

2
+1: це корисна відповідь. Але: якщо тип вихідного параметра є класом, а не вбудованим типом типу рядка - я не вірю, що це спрацює. Спробував це сьогодні. Об'єкт макету імітує виклик і повертає нуль через параметр "out".
ажеглов

86

EDIT : У Moq 4.10 тепер ви можете передавати делегата, який має параметр out або ref безпосередньо до функції зворотного виклику:

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

Вам потрібно буде визначити делегата та інстанціювати його:

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

Для версії Moq до 4.10:

Авнер Каштан надає у своєму блозі метод розширення, який дозволяє встановити параметр вихід із зворотного виклику: параметри Moq, зворотні дзвінки та вихід: особливо складний край

Рішення одночасно елегантне і хакі. Елегантний тим, що надає вільний синтаксис, який відчуває себе вдома з іншими зворотними викликами Moq. І хакі, тому що він покладається на виклик деяких внутрішніх API Moq через рефлексію.

Спосіб розширення, який надано у вищенаведеному посиланні, для мене не компілював, тому я надав редаговану версію нижче. Вам потрібно буде створити підпис для кожної кількості вхідних параметрів; Я надав 0 і 1, але продовжити його слід просто:

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

За допомогою описаного вище способу розширення ви можете протестувати інтерфейс із такими параметрами, як:

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

.. із такою настройкою 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);
    }



Редагувати : Щоб підтримувати методи повернення недійсності, вам просто потрібно додати нові методи перевантаження:

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

Це дозволяє тестувати інтерфейси, такі як:

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, я оновив свою відповідь додатковими перевантаженнями для функцій зворотного повернення.
Скотт Вегнер

2
Я використовував це рішення в нашому тестовому наборі і працював. Однак після оновлення до Moq 4.10 це більше не відбувається.
Рістогод

2
Схоже, це було порушено в цьому комітеті github.com/moq/moq4/commit/… . Може, є кращий спосіб зробити це зараз?
свічка запалювання

1
fyi в Moq4, MethodCall тепер є властивістю установки, тому кишки OutCallbackInternal вище змінюються наvar 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, з оновленням до Moq 4.10, тепер ви можете передати делегат , який має поза або реф параметр безпосередньо в функцію зворотного виклику: mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);Ви повинні визначити делегат і створити його екземпляр:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
esteuart

48

Це документація з сайту 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
Це в основному те саме, що відповідь Parched і має те саме обмеження, що воно не може змінити значення виходу залежно від вхідних даних, а також не може відповідати параметрам ref.
Річард Шалай

@Richard Szalay, Можливо, але вам потрібно мати окремі установки з окремими параметрами "outString"
Sielu

17

Здається, це неможливо поза коробкою. Схоже, хтось спробував рішення

Дивіться це повідомлення на форумі http://code.google.com/p/moq/isissue/detail?id=176

це питання Перевірте значення контрольного параметра за допомогою Moq


Дякуємо за підтвердження. Я фактично знайшов ці два посилання під час мого пошуку, але також помітив, що Moq перераховує одну з його функцій як "підтримує параметри відмови / виходу", тому я хотів бути впевненим.
Річард Салай

3

Щоб повернути значення разом із встановленням параметра ref, ось фрагмент коду:

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

Потім оголосіть власного делегата, що відповідає підпису методу, що підлягає глузді, та надайте власну реалізацію методу.

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

Чи працює це, коли у вас немає доступу до змінної, яка буде передана як y? У мене є функція, яка приймає два аргументи ref до DayOfWeek. Мені потрібно встановити обидва ці моменти на конкретний день у заглушці макета, а третій аргумент - це контекст бази даних макетів. Але метод делегата просто не викликається. Схоже, Moq розраховував би відповідати локальному y, переданому вашій функції "MyMethod". Це так працює для вашого прикладу? Дякую.
Грег Верес

3

Спираючись на тендера Біллі Джейкса, я здійснив повністю динамічний макетний метод із зовнішнім параметром. Я публікую це тут для тих, хто вважає це корисним (напевно, я просто майбутнє).

// 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

Я впевнений, що рішення Скотта спрацювало в один момент,

Але це хороший аргумент для того, щоб не використовувати рефлексію, щоб зазирнути на приватні апіси. Зараз це зламано.

Мені вдалося встановити параметри за допомогою делегата

      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

Це може бути рішенням.

[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
Це в основному те саме, що відповідь Parched і має те саме обмеження, що воно не може змінити значення виходу залежно від вхідних даних, а також не може відповідати параметрам ref.
Річард Шалай

1

Я боровся з багатьма пропозиціями тут, перш ніж я просто створив екземпляр нового класу 'Fake', який реалізує будь-який інтерфейс, з якого ви намагаєтеся знущатися. Тоді ви можете просто встановити значення параметра out із самим методом.


0

Я боровся з цим цілу годину сьогодні вдень і не міг знайти відповіді ніде. Після самої гри з цим я зміг придумати рішення, яке працювало на мене.

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

Ключовим тут є те, mock.SetupAllProperties();що заглушить усі властивості для вас. Це може не спрацювати в кожному конкретному випадку сценарії тесту, але якщо все , що ви дбаєте про отримання return valueз , YourMethodто це буде чудово працювати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.