-->

Unit Testing BLL: mock Repository, UnitOfWork, Uni

2019-08-31 04:09发布

问题:

I need a jump start in testing the methods on my Business layer. Consider the Materials BLL object, how can I test the AddNewMaterial method for it?

interface IGenericRepository<TEntity>
{
    TEntity Add(TEntity m); 
}

public interface IMaterialRepository : IGenericRepository<Material>
{
}

public interface IUnitOfWork
{
    IMaterialRepository Materials { get; private set;}

    void Save();
}

public interface IUnitOfWorkFactory
{
    IUnitOfWork GetUnitOfWOrk();
}

public class MaterialsBLL
{
    private readonly IUnitOfWorkFactory _uowFactory;

    //uowFactory comes from DI
    public MaterialsBLL(IUnitOfWorkFactory uowFactory)
    {
        _uowFactory = uowFactory;
    }

    //TODO: test this
    public Material AddNewMaterial(Material m)
    {
        using(var uow = _uowFactory.GetUnitOfWOrk())
        {
            var result = uow.Materials.Add(m);
            uow.Save();
            return result;
        }
    }

I am using Moq, and XUnit, but am very green. In general I want to do this:

  1. Mock the repositories Add method.
  2. Mock the UoW Materials property to return my repository mock.
  3. Mock the UoWFactory to return the UoW mock.
  4. Create the MaterialsBLL giving the mocked UoWFactory to the contstructor.
  5. Verify that the AddNewMaterials calls the repository's Add, and the UoW's Save, etc.

It seems to me that, I maybe should be creating a Fake MaterialRepository, rather than mocking it? Any other advice? Here is a first crack:

    [Fact]
    public void TestGetMaterialById()
    {
        var materialList = GetMaterials();

        var materialRepositoryMock = new Mock<IMaterialRepository>();
        materialRepositoryMock.Setup(repo => repo.Get(4)).Returns(materialList.First());

        var uowMock = new Mock<IUnitOfWork>();
        uowMock.SetupProperty<IMaterialRepository>(uow => uow.Materials, materialRepositoryMock.Object);

        var uowFactoryMock = new Mock<IUnitOfWorkFactory>();
        uowFactoryMock.Setup(f => f.GetUnitOfWork()).Returns(uowMock.Object);

        var materialsBll = new Materials(uowFactoryMock.Object);
        var result = materialsBll.Get(4);

        Assert.Equal(result.MaterialId, 4);
        Assert.Equal(result.Name, "Four");
    }

回答1:

When you feel like you need several levels of nested mock objects, there's generally something wrong with your design.

The Law of Demeter warns us here that you should probably not tinker with uow.Materials in MaterialsBLL.

Besides, a Unit of Work is typically not the place to expose Repositories. The code that needs to access Materials will usually have a direct reference to an IMaterialsRepository, not ask it from the UoW, and then the Repository implementation might reference the UoW internally.

This leads to a flatter design and simplifies your production code as well as your tests.