0

我正在尝试测试AddCategory以下内容CategoryService

我的问题是我很难理解要模拟/伪造什么。

我的测试尝试在底部。

我正在使用最小起订量、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);
    }
}

验证者工厂

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

类别验证器

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

单元测试尝试

[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);
    }
4

1 回答 1

3

对于 AddCategory 方法,我认为您实际上只需要两个模拟,一个用于 ValidationService,一个用于 CategoryRepository,因为其他依赖项不在该函数中执行,因此无关紧要

(当然,如果您的 ctor 抛出空参数,情况可能会有所不同,但在这种情况下,我认为您没问题 - 尽管您可能会考虑在未来添加这些检查:)

无论如何,作为迂腐的,我几乎倾向于为这个函数编写两个(或更多 - 可能一个用于 null 输入以验证它抛出或返回 false 或其他)“单元”测试;

  • 一个验证给定无效类别的函数返回false,
  • 为了验证给定一个有效的类别,该函数在 CategoryRepository 依赖项上调用 Add。

所以它看起来像这样(对不起,这是使用 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 ;)
}

我赞成这种方法的原因是因为它迫使您考虑被测方法的行为,并确保您不涉及任何未测试的依赖项,而且这意味着您的测试方法只创建他们需要的东西为了运行测试(当然,您可以创建一些设置/拆卸助手来为您预先创建这些模拟);

当然,您可以将以上所有内容放在一个方法中,但为了节省一些 LOC,我希望您同意使用两个单独的测试来验证两个单独的行为是一种更强大的方法。

只是我的2c。希望能帮助到你!

于 2013-09-03T23:42:08.343 回答