Подтвердить что ты не робот

Проверка вызова метода с помощью выражения Lambda - 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 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 called (PASSES):
public Questionnaire GetFromType(string type)
{
    // this will pass the Verify
    var questionnaire = _session
        .Single<Questionnaire>(q => q.Type == QuestionnaireType.Objective);
}

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

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

4b9b3361

Ответ 1

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

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

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

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

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

UPDATE. На ваше обновление рассмотрите следующие настройки и выражения:

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 в правой части выражения.

Ответ 2

Я также хотел бы поделиться другим подходом к сравнению выражения параметра с ожидаемым выражением. Я искал StackOverflow для "как сравнивать выражения", и я привел к этим статьям:

Затем меня привели в этот репозиторий Subversion для db4o. нетто. В одном из своих проектов пространство имен 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() возвращают true в этом случае (с ToString скорее всего, быстрее - скорость не тестируется). Лично я предпочитаю метод сравнения, так как считаю, что он более самодокументирован и лучше отражает ваше намерение дизайна (сравнение объектов выражения, а не сравнение строк их выходных данных ToString).

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