Методы Moq'ing, в которых Expression <Func <T, bool >> передаются как параметры - PullRequest
59 голосов
/ 04 марта 2011

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

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

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

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

Так что я проверяю логику самой RegistrationLogic.AddNewCompany (), а не логику и конкретный репозиторий, я копирую IRepository и передаю его в RegistrationLogic. Макетированный репозиторий выглядит так:

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

, который возвращает IEnumberable в памяти, содержащий объект Company с именем, установленным в «Company Inc». Модульный тест, который вызывает RegistrationLogic.AddNewCompany, устанавливает компанию с дублирующими данными и пытается передать это, и я утверждаю, что ArgumentException генерируется с сообщением «Компания уже существует». Этот тест не пройден.

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

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

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

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

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

Ответы [ 3 ]

73 голосов
/ 04 марта 2011

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

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

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

7 голосов
/ 04 марта 2011

Вы должны иметь возможность использовать 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());
2 голосов
/ 04 марта 2011

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

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

repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc").Returns(....
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...