0

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");
    }
sheamus
  • 3,001
  • 4
  • 31
  • 54

1 Answers1

0

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.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
  • "When you feel like you need several levels of nested mock objects, there's generally something wrong with your design." yeah, I was getting that feeling. – sheamus May 08 '14 at 13:50
  • So that would mean creating MaterialsBLL passing an IMaterialsRepository, into it's constructor, and passing a IUoW into the Repo constructor (all via DI). I had my IUoW as a member of the BLL, so it should likely move into the DAL. The reason for the UoWFactory in the BLL was so that you could create a MaterialsBLL in Presentation layer without referencing the DAL. – sheamus May 08 '14 at 14:07
  • The beauty of DI is that you can have an interface declared in one layer and implementations residing in others. IUoW can be in your BLL layer and concrete UoW in the DAL (or in a separate Infrastructure layer). – guillaume31 May 08 '14 at 15:27
  • Also note that it may be a good idea to have an intermediate Application layer hosting your use cases (`AddNewMaterial` is typically one) and manipulating the unit of work. The reasoning behind this is that the Business Logic (or Domain) layer should have no knowledge of the current execution context of the application and shouldn't decide to commit or rollback current applicative transaction. See http://stackoverflow.com/questions/17645978/how-to-use-unit-of-work-in-domain-layer second answer. – guillaume31 May 08 '14 at 15:32