Методы Moq'ing, в которых выражение <Func <T, bool >> передается как параметры

Я очень новичок в модульном тестировании и насмешливости! Я пытаюсь написать некоторые модульные тесты, которые охватывают некоторый код, который взаимодействует с хранилищем данных. Доступ к данным инкапсулируется IRepository:

interface IRepository<T> {
    ....
    IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
    ....
}

Код, который я пытаюсь проверить, используя конкретную реализацию IoC'd IRepository, выглядит следующим образом:

public class SignupLogic {
    private Repository<Company> repo = new Repository<Company>();

    public void AddNewCompany(Company toAdd) {
        Company existingCompany = this.repo.FindBy(c => c.Name == toAdd.Name).FirstOrDefault();

        if(existingCompany != null) {
            throw new ArgumentException("Company already exists");
        }

        repo.Add(Company);
        repo.Save();
    }
}

Итак, я тестирую логику самого RegisterLogic.AddNewCompany(), а не логику и конкретный репозиторий, я издеваюсь над IRepository и передаю его в RegistrationLogic. Исправленный репозиторий выглядит следующим образом:

Mock<Repository> repoMock = new Mock<Repository>();
repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc")....

который возвращает IE-номер в памяти, содержащий объект Company с именем, установленным в "Company Inc". unit test, который вызывает RegistrationLogic.AddNewCompany, создает компанию с повторяющимися данными и пытается передать ее, и я утверждаю, что ArgumentException вызывается сообщением "Компания уже существует". Этот тест не проходит.

Отладка через unit test и AddNewCompany() при запуске будет казаться, что existingCompany всегда имеет значение null. В отчаянии я обнаружил, что если я обновляю RegistrationLogic.AddNewCompany(), чтобы вызов FindBy выглядел следующим образом:

Company existingCompany = this.repo.FindBy(c => c.Name == "Company Inc").FirstOrDefault();

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

Я попытался настроить moq.FindBy(...) для использования "Is.ItAny", но это не приводит к тому, что тест пройдет.

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

Ответ 1

Возможно, правильно, что будет соответствовать только Expression с точно такой же структурой (и буквальными значениями). Я предлагаю вам использовать перегрузку Returns(), которая позволяет использовать параметры, с которыми вызывается mock:

repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())
        .Returns((Expression<Func<Company, bool>> predicate) => ...);

В ... вы можете использовать predicate, чтобы вернуть соответствующие компании (и, возможно, даже выбросить исключение, если соответствующие компании не так, как вы ожидали). Не очень красиво, но я думаю, что это сработает.

Ответ 2

Вы можете использовать It.IsAny<>(), чтобы выполнить то, что вы хотите сделать. С помощью It.IsAny<>() вы можете просто настроить тип возврата для своей установки, чтобы протестировать каждую ветвь вашего кода.

It.IsAny<Expression<Func<Company, bool>>>()

Первый тест, верните компанию независимо от предиката, который вызовет исключение:

var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>{new Company{Name = "Company Inc"}});
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
//Assert the exception was thrown.

Второй тест, сделайте возвращаемый тип пустым списком, который вызовет вызов add.:

var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>());
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
repoMock.Verify(r => r.Add(It.IsAny<Company>()), Times.Once());

Ответ 3

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

В рамках Moq. Важно поставить .Returns() для функций, иначе он не будет соответствовать. Поэтому, если вы этого не сделали, это ваша проблема.

repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc").Returns(....