Проверить вызов метода с помощью лямбда-выражения - Moq

У меня есть реализация Unit of Work, среди прочего, со следующим методом:

T Single<T>(Expression<Func<T, bool>> expression) where T : class, new();

и я называю это, например, так:

var person = _uow.Single<Person>(p => p.FirstName == "Sergi");

Как я могу проверить, что метод Single был вызван с аргументом FirstName == "Sergi"?

Я пробовал следующее, но безуспешно:

// direct approach 
session.Verify(x => x.Single<Person>(p => p.FirstName == "Sergi"));

// comparing expressions
Expression<Func<Person, bool>> expression = p => p.FirstName == "Sergi");

session.Verify(x => x
    .Single(It.Is<Expression<Func<Person, bool>>>(e => e == expression));

Все они приводят к следующей ошибке:

Ожидается вызов на макете хотя бы один раз, но так и не был выполнен

Есть идеи, как это можно сделать? Я использую последнюю версию Moq от NuGet версии 4.0.10827.0.

ОБНОВЛЕНИЕ: конкретный пример

Я вижу, что всякий раз, когда я использую строковые литералы внутри лямбды, Verify работает. Как только я сравниваю переменные, ничего не получается. Дело в точке:

// the verify
someService.GetFromType(QuestionnaireType.Objective)

session.Verify(x => x.Single<Questionnaire>(q => 
    q.Type == QuestionnaireType.Objective));


// QuestionnaireType.Objective is just a constant:
const string Objective = "objective";


// the method where it's called (FAILS):
public Questionnaire GetFromType(string type)
{
    // this will fail the Verify
    var questionnaire = _session
        .Single<Questionnaire>(q => q.Type == type);
}

// the method where it's called (PASSES):
public Questionnaire GetFromType(string type)
{
    // this will pass the Verify
    var questionnaire = _session
        .Single<Questionnaire>(q => q.Type == QuestionnaireType.Objective);
}

Почему Verify выходит из строя, как только я использую параметр метода в лямбда-выражении?

Как правильно написать этот тест?


person Sergi Papaseit    schedule 11.07.2011    source источник


Ответы (2)


Прямой подход мне подходит:

// direct approach 
session.Verify(x => x.Single<Person>(p => p.FirstName == "Sergi"));

Объект выражения не возвращает истину для эквивалентных выражений, поэтому это не удастся:

// comparing expressions
Expression<Func<Person, bool>> expression = p => p.FirstName == "Sergi");

session.Verify(x => x
    .Single(It.Is<Expression<Func<Person, bool>>>(e => e == expression));

Чтобы понять почему, запустите следующий тест NUnit:

[Test]
public void OperatorEqualEqualVerification()
{
    Expression<Func<Person, bool>> expr1 = p => p.FirstName == "Sergi";
    Expression<Func<Person, bool>> expr2 = p => p.FirstName == "Sergi";
    Assert.IsTrue(expr1.ToString() == expr2.ToString());
    Assert.IsFalse(expr1.Equals(expr2));
    Assert.IsFalse(expr1 == expr2);
    Assert.IsFalse(expr1.Body == expr2.Body);
    Assert.IsFalse(expr1.Body.Equals(expr2.Body));
}

И, как показывает приведенный выше тест, сравнение по телу выражения также не удастся, но сравнение строк работает, так что это тоже работает:

// even their string representations!
session.Verify(x => x
    .Single(It.Is<Expression<Func<Person, bool>>>(e => 
        e.ToString() == expression.ToString()));

И вот еще один стиль тестирования, который вы можете добавить в свой арсенал, который также работает:

[Test]
public void CallbackVerification()
{
    Expression<Func<Person, bool>> actualExpression = null;
    var mockUow = new Mock<IUnitOfWork>();
    mockUow
        .Setup(u => u.Single<Person>(It.IsAny<Expression<Func<Person, bool>>>()))
        .Callback( (Expression<Func<Person,bool>> x) => actualExpression = x);
    var uow = mockUow.Object;
    uow.Single<Person>(p => p.FirstName == "Sergi");

    Expression<Func<Person, bool>> expectedExpression = p => p.FirstName == "Sergi";

    Assert.AreEqual(expectedExpression.ToString(), actualExpression.ToString());
}

Поскольку у вас есть несколько тестовых примеров, которые не должны работать, скорее всего, у вас другая проблема.

ОБНОВЛЕНИЕ. При обновлении учитывайте следующие настройки и выражения:

string normal_type = "NORMAL";
// PersonConstants is a static class with NORMAL_TYPE defined as follows:
// public const string NORMAL_TYPE = "NORMAL";
Expression<Func<Person, bool>> expr1 = p => p.Type == normal_type;
Expression<Func<Person, bool>> expr2 = p => p.Type == PersonConstants.NORMAL_TYPE;

Одно выражение ссылается на переменную экземпляра содержащего метода. Другой представляет выражение, которое ссылается на константный член статического класса. Это два разных выражения, независимо от значений, которые могут быть присвоены переменным во время выполнения. Если, однако, string normal_type изменяется на const string normal_type, тогда выражения снова будут такими же, как и каждая ссылка a const в правой части выражения.

person Kaleb Pederson    schedule 11.07.2011
comment
Большое спасибо за ваш ответ. Я обновил свой вопрос на основе новых э ... результатов, за неимением лучшего слова. Любые идеи? - person Sergi Papaseit; 11.07.2011
comment
Я обновил свой вопрос, чтобы выяснить, в чем может быть ваша проблема. Это действительно зависит от типа QuestionnaireType.Objective. Я ожидаю, что если вы ToString() два выражения, вы увидите, что они имеют разные типы. - person Kaleb Pederson; 12.07.2011
comment
Еще раз спасибо, я полагаю, это имеет смысл. Но как тогда правильно написать этот тест? Я чувствую, что в данном случае издевательство - это препятствие, а не средство для более четкого кода ... - person Sergi Papaseit; 12.07.2011
comment
+1 за прямой подход. это работает и кажется самым простым решением (обычно правильным) - person geekzster; 28.09.2016
comment
+1 для напоминания об использовании Callback(), когда вам нужно проверить, какое значение результат частного вспомогательного метода привел к входному параметру - person gabe; 24.09.2020

Я также хотел бы поделиться другим подходом к сравнению выражения параметра с ожидаемым выражением. Я искал в StackOverflow «как сравнивать выражения» и нашел следующие статьи:

Затем меня привели к этот репозиторий Subversion для db4o.net. В один из своих проектов, пространство имен Db4objects.Db4o.Linq.Expressions, они включают класс с именем ExpressionEqualityComparer. Мне удалось получить этот проект из репозитория, скомпилировать, построить и создать DLL для использования в моем собственном проекте.

С помощью ExpressionEqualityComparer вы можете изменить вызов Verify на что-то вроде следующего:

session.Verify(x => x .Single(It.Is<Expression<Func<Person, bool>>>(e => new ExpressionEqualityComparer().Equals(e, expression))));

В конечном счете, методы ExpressionEqualityComparer и ToString() в этом случае возвращают истину (причем ToString, скорее всего, быстрее - скорость не проверялась). Лично я предпочитаю подход компаратора, поскольку я считаю, что он более самодокументирован и лучше отражает ваш замысел (сравнение объектов выражений, а не строковое сравнение их выходных данных ToString).

Примечание. Я все еще ищу файл лицензии db4o.net в этом проекте, но я никоим образом не изменял код, не включал уведомление об авторских правах и (поскольку страница общедоступна) Полагаю, на данный момент этого достаточно ... ;-)

person Pflugs    schedule 10.06.2014
comment
Лично я думаю, что в MOQ следует добавить этот ExpressionComparer и использовать его при использовании установки и проверки. - person Robetto; 09.02.2020