繁体   English   中英

模拟单元测试

[英]Mocking in Unit Tests

我正在尝试测试以下CategoryServiceAddCategory

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

我在测试中的尝试是在底部。

我正在使用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);
    }
}

验证工厂

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

对于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。 希望能帮助到你!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM