Domanda

I am trying to test the AddCategory of the following CategoryService.

My problem is that I am having a hard time understanding what to mock/fake.

My attempt at the test is at the bottom.

I am using MOQ, xUnit and FluentAssertions.

I am using FluentValidation for the validators.

Category Service

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

Validation Service

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 Factory

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

Category Validator

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

Unit Test Attempt

[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);
    }
È stato utile?

Soluzione

Well for the AddCategory method, I think you really only need two mocks, one for the ValidationService, and one for the CategoryRepository, because the other dependencies aren't exercised in that function and therefore are irrelevant

(the story might be different of course if your ctor throws on null arguments but in this case I think you are OK - albeit you might consider adding these checks in the future :)

Anyway, being pedantic, I'd nearly be inclined to write two (or more - maybe one for null input to verify it throws or returns false or whatever) "unit" tests for this function;

  • One to verify that given an invalid category, the function returns false,
  • One to verify that given a valid category, the function calls Add on the CategoryRepository dependency.

So it would look like this (sorry, this is using MSTest syntax as I'm not familiar with xUnit but it's the same idea). Also have not tested below for typos, etc :)

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

The reason I favour this approach is because it forces you to consider the behaviour of the method under test, as well as ensuring that you don't involve any dependencies that are not being tested plus it means your test methods only create exactly what they need to in order to run the tests (and of course you can create some setup/teardown helpers to pre-create those mocks for you);

Of course you could put all the above into a single method but for the sake of saving a few LOC I hope you'll agree that having two separate tests to verify two separate behaviours is a more robust approach.

Just my 2c. hope it helps!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top