模拟单元测试

时间:2013-09-02 16:55:37

标签: unit-testing moq fluentvalidation xunit.net

我正在尝试测试以下AddCategory的{​​{1}}。

我的问题是我很难理解什么是假/假。

我在测试中的尝试位于底部。

我正在使用MOQ,xUnit和FluentAssertions。

我正在使用FluentValidation作为验证器。

类别服务

CategoryService

验证服务

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

1 个答案:

答案 0 :(得分:3)

对于AddCategory方法,我认为真的只需要两个模拟,一个用于ValidationService,一个用于CategoryRepository,因为其他依赖项不在该函数中执行,因此不相干

(如果你的ctor抛出空参数,那么故事可能会有所不同,但在这种情况下我认为你没事 - 尽管你可能会考虑将来添加这些检查:)

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

  • 一个用于验证给定无效类别,该函数返回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。希望它有所帮助!