2013-09-02 2 views
0

Я пытаюсь проверить AddCategory следующих CategoryService.Mocking in Unit Tests

Моя проблема в том, что мне трудно понять, что насмехаться/подделывать.

Моя попытка испытания находится внизу.

Я использую MOQ, xUnit и FluentAssertions.

Я использую FluentValidation для валидаторов.

Категория Сервис

public class CategoryService : ValidatingServiceBase, ICategoryService 
{ 
    private readonly IUnitOfWork unitOfWork; 
    private readonly IRepository<Category> categoryRepository; 
    private readonly IRepository<SubCategory> subCategoryRepository; 
    private readonly IValidationService validationService; 

    public CategoryService(
     IUnitOfWork unitOfWork, 
     IRepository<Category> categoryRepository, 
     IRepository<SubCategory> subCategoryRepository, 
     IValidationService validationService) 
     : base(validationService) 
    { 
     this.unitOfWork = unitOfWork; 
     this.categoryRepository = categoryRepository; 
     this.subCategoryRepository = subCategoryRepository; 
     this.validationService = validationService; 
    } 

    public bool AddCategory(Category category) 
    { 
     var validationResult = validationService.Validate(category); 

     if (!validationResult.IsValid) 
     { 
      return false; 
     } 
     else 
     { 
      categoryRepository.Add(category); 
      return true; 
     } 
    } 

    public bool DoesCategoryExist(string categoryName) 
    { 
     return categoryRepository.Query().SingleOrDefault(x => x.Name == categoryName) != null; 
    } 
} 

Сервис проверки

public class ValidationService : ServiceBase, IValidationService 
{ 
    private readonly IValidatorFactory validatorFactory; 

    public ValidationService(IValidatorFactory validatorFactory) 
    { 
     Enforce.ArgumentNotNull(validatorFactory, "validatorFactory"); 

     this.validatorFactory = validatorFactory; 
    } 

    public ValidationResult Validate<TEntity>(TEntity entity) where TEntity : class 
    { 
     var validator = validatorFactory.GetValidator<TEntity>(); 
     return validator.Validate(entity); 
    } 
} 

Validator завод

public class ValidatorFactory : IValidatorFactory 
{ 
    public IValidator GetValidator(Type type) 
    { 
     Enforce.ArgumentNotNull(type, "type"); 

     return DependencyResolver.Current.GetService(typeof(IValidator<>).MakeGenericType(type)) as IValidator; 
    } 

    public IValidator<T> GetValidator<T>() 
    { 
     return DependencyResolver.Current.GetService<IValidator<T>>(); 
    } 
} 

Категория Validator

public class CategoryValidator : AbstractValidator<Category> 
{ 
    public CategoryValidator(ICategoryService service) 
    { 
     RuleFor(x => x.Name) 
      .NotEmpty() 
      .Must((category, name) => 
      { 
       return service.DoesCategoryExist(name); 
      }); 
    } 
} 

Unit Test Покушение

[Fact] 
    public void AddCategory_Should_ReturnTrue() 
    { 
     var category = new Category() { Name = "Cat1" }; 

     var unitOfWork = new Mock<IUnitOfWork>(); 
     var categoryRepo = new Mock<IRepository<Category>>(); 
     var subCategoryRepo = new Mock<IRepository<SubCategory>>(); 

     var mockCategoryService = new Mock<ICategoryService>(); 
     var categoryValidator = new CategoryValidator(mockCategoryService.Object); 

     var validatorFactory = new Mock<IValidatorFactory>(); 
     validatorFactory.Setup(x => x.GetValidator<CategoryValidator>()).Returns(categoryValidator as IValidator<CategoryValidator>); 

     var validationService = new ValidationService(validatorFactory.Object); 

     var categoryService = new CategoryService(
      unitOfWork.Object, 
      categoryRepo.Object, 
      subCategoryRepo.Object, 
      validationService); 

     categoryService.AddCategory(category); 
    } 

ответ

3

Ну для метода AddCategory, я думаю, что вы действительно нужно только два издевается, один для ValidationService, и один для CategoryRepository, поскольку другие зависимости не выполняются в этой функции и поэтому неактуальны

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

Во всяком случае, будучи педантичным, я бы почти склонны писать два (или более - возможно, один для нулевого ввода, чтобы проверить, что он выбрасывает или возвращает ложные или другие) «единицы» для этой функции;

  • Один, чтобы убедиться, что данный недопустимая категория, функция возвращает ложь,
  • Один, чтобы убедиться, что данный корректную категорию, вызовы функций Добавить на зависимость CategoryRepository.

Таким образом, это будет выглядеть так (извините, это использует синтаксис MSTest, поскольку я не знаком с xUnit, но это та же идея).Также не тестировали ниже опечатки и т.д. :)

public void AddCategory_InvalidCategory_ShouldReturnFalse() 
{ 
//Arrange 
    var mockValidator = new Mock<IValidator>(); 
//no matter what we pass to the validator, it will return false 
    mockValidator.Setup(v=>v.Validate(It.IsAny<Category>()).Returns(false); 
    var sut= new CategoryService(null,null,null,mockValidator.Object); 
    bool expected = false; 

//ACT 
    bool actual = sut.AddCategory(new Category()); 

//ASSERT 
    Assert.AreEqual(expected,actual,"Validator didn't return false as expected"); 

} 

public void AddCategory_ValidCategory_ShouldCallRepositoryAdd() 
{ 
//Arrange 
    var mockValidator = new Mock<IValidator>(); 
//no matter what we pass to the validator, it will return true 
    mockValidator.Setup(v=>v.Validate(It.IsAny<Category>()).Returns(true); 
    var mockRepo = new Mock<IRepository<SubCategory>>(); 
    mockRepo.Setup(r=>r.Add(It.IsAny<Category>())); //do not know or care what happens as this is a void method. 
    var sut= new CategoryService(null,mockRepo.Object,null,mockValidator.Object); 
bool expected = false; 

//ACT 
    bool actual = sut.AddCategory(new Category()); 

//ASSERT 
    mockRepo.Verify(r=>r.Add(It.IsAny<Category>(),Times.Exactly(1),"Repo ADD method not called or called too many times, etc"); 
    Assert.AreEqual(expected,actual,"Add was called BUT the AddCategoryMethod didn't return true as expected"); //and of course you could be totally pedantic and create a new test method for that last assert ;) 
} 

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

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

Только мой 2c. Надеюсь, поможет!

+0

Итак, в моем методе 'AddCategory' на службе мне не нужно проверять внутреннюю работу метода? Как 'ValidationService'' Validators'? Просто высмеивать 'ValidationService', чтобы он возвращался корректно или нет, а затем проверить, что делает метод' AddCategory' на основе этого? Нужно ли мне только проверять результат, возвращаемый методом «AddCategory»? Что, если бы это был метод пустоты, это изменит способ его проверки? – Sam

+0

@Sam - более или менее. Вы косвенно проверяете внутреннюю работу посредством * поведенческой проверки. Ключевой вопрос - что такое ** минимальный набор фактов, который я должен подтвердить, чтобы убедиться, что метод ведет себя так, как я его разработал? Я заметил в своем предыдущем ответе, что я предположил, что Validator вернул bool - он возвращает объект, но вы можете просто сказать макету вернуть сам объект состояния проверки состояния, принцип тот же. –