Как мне создать метод с необязательным аргументом в сигнатуре без явного его указания или использования перегрузки?

119

Учитывая следующий интерфейс:

public interface IFoo
{
    bool Foo(string a, bool b = false);
}

Попытка издеваться над ним с помощью Moq:

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>())).Returns(false);

дает следующую ошибку во время компиляции:

Дерево выражения не может содержать вызов или вызов, использующий необязательные аргументы.

Я обнаружил, что проблема выше поднята как улучшение в списке проблем Moq, и похоже, что она относится к выпуску 4.5 (когда бы то ни было).

Мой вопрос: что мне делать, учитывая, что вышеуказанное не будет исправлено в ближайшее время? Есть ли у меня варианты только либо явно устанавливать значение по умолчанию для необязательного параметра каждый раз, когда я издеваться над ним (какой вид побеждает точку указания его в первую очередь), либо создавать перегрузку без bool (например, что я сделал бы до C # 4)?

Или кто-нибудь встречал более умный способ побороть эту проблему?

Appulus
источник
5
Было бы разумно просто указать It.IsAny <bool> () для второго параметра?
Поль д'Ост 05
Спустя полтора года это все еще верно ..
Мукус
@Mukus, не стесняйтесь писать пиар, братан.
IamDOM

Ответы:

91

Я считаю, что сейчас ваш единственный выбор - явно включить boolпараметр в настройку для Foo.

Я не думаю, что это противоречит цели указания значения по умолчанию. Значение по умолчанию - это удобство для вызова кода, но я думаю, что вы должны быть явными в своих тестах. Скажем, вы можете не указывать boolпараметр. Что произойдет, если в будущем кто-то изменит значение по умолчанию bна true? Это приведет к непройденным тестам (и это справедливо), но они будут более трудно исправить из - за скрытое предположение , что bесть false. Явное указание boolпараметра имеет еще одно преимущество: оно улучшает читаемость ваших тестов. Кто-то, просматривая их, быстро поймет, что есть одна Fooфункция, которая принимает два параметра. По крайней мере, это мои 2 цента :)

Что касается указания его каждый раз, когда вы имитируете его, не дублируйте код: создайте и / или инициализируйте макет в функции, чтобы у вас была только одна точка изменения. Если вы действительно хотите, вы можете преодолеть очевидный недостаток Moq, скопировав Fooпараметры в эту функцию инициализации:

public void InitFooFuncOnFooMock(Mock<IFoo> fooMock, string a, bool b = false)
{
    if(!b)
    {
        fooMock.Setup(mock => mock.Foo(a, b)).Returns(false);
    }
    else
    {
        ...
    }
}
Крис Мантл
источник
1
Отличный ответ; Я уже пошел дальше и явно указал это в своих макетах, но ваш ответ очень ясно и логично подтверждает, почему я должен делать это таким образом. Спасибо, @Chris.
Appulus
9
изменение параметра по умолчанию "должно" нарушить тесты. Отсутствие неудачных тестов при изменении значения по умолчанию может быть признаком плохого тестирования. Код может использовать значения по умолчанию, а тесты - нет?
Pop Catalin
Прошло некоторое время, но я пробовал этот подход с Moq при попытке имитировать интерфейс (IDConnection в Dapper), и я все еще получаю ту же ошибку. Есть идеи, почему? Пример строки: mockDB.Setup (x => x.Query <MyObject> (It.IsAny <string> (), It.IsAny <DynamicParameters> (), It.IsAny <IDbTransaction> (), false, 600)). Возвраты (новый список <MyObject> ()); Последние два значения - это необязательные параметры настраиваемого мной метода.
Raelshark 07
4
Arrgggh! Ужасный if (!x) {} else {}
антипаттерн
1
@ nicodemus13 Да, но я старался, чтобы пример кода был как можно ближе к примеру OP в вопросе. Я бы не стал его защищать :)
Крис Мантл
8

Только что столкнулся с этой проблемой сегодня, Moq не поддерживает этот вариант использования. Таким образом, кажется, что переопределения метода было бы достаточно в этом случае.

public interface IFoo
{
    bool Foo(string a);

    bool Foo(string a, bool b);
}

Теперь доступны оба метода, и этот пример будет работать:

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>())).Returns(false);
Гил Гроссман
источник
2

Используя Moq версии 4.10.1, я смог сделать следующее

С интерфейсом:

public interface IFoo
{
    bool Foo(string a, bool b = false);
}

И издеваться

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>(), It.IsAny<bool>())).Returns(false);

Разрешает вызов Foo с первым параметром, хорошо

CF5
источник