Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test EF Repository pattern with Moq

I decided to start writing unit tests in our application. It uses Entity Framework with a repository pattern.

Now I want to start testing logic classes which are using the repositories. I provide a simple example here.

Three of my methods in the class GenericRepository:

public class GenericRepository : IRepository
{
    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        var entityName = GetEntityName<TEntity>();
        return Context.CreateQuery<TEntity>(entityName);
    }
    private string GetEntityName<TEntity>() where TEntity : class
    {
        return typeof(TEntity).Name;
    }
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

A simple logic class returning distinct years from a calendar table in descending order (yes I know the word calendar is misspelled in our code):

public class GetDistinctYearsFromCalendar
{
    private readonly IRepository _repository;

    public GetDistinctYearsFromCalendar()
    {
        _repository = new GenericRepository();
    }

    internal GetDistinctYearsFromCalendar(IRepository repository)
    {
        _repository = repository;
    }

    public int[] Get()
    {
        return _repository.Find<Calender_Tbl>(c => c.Year.HasValue).Select(c => c.Year.Value).Distinct().OrderBy(c => c).Reverse().ToArray();
    }
}

And here is my first test:

[TestFixture]
public class GetDistinctYearsFromCalendarTest
{
    [Test]
    public void ReturnsDistinctDatesInCorrectOrder()
    {
        var repositoryMock = new Mock<IRepository>();

        repositoryMock.Setup(r => r.Find<Calender_Tbl>(c => c.Year.HasValue)).Returns(new List<Calender_Tbl>
        {
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2010, 1, 1),
                  Year = 2010
              },
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2010, 2, 1),
                  Year = 2010
              },
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2011, 1, 1),
                  Year = 2011
              }
        }.AsQueryable());

        var getDistinct = new GetDistinctYearsFromCalendar(repositoryMock.Object).Get();

        Assert.AreEqual(2, getDistinct.Count(), "Returns more years than distinct.");
        Assert.AreEqual(2011, getDistinct[0], "Incorrect order, latest years not first.");
        Assert.AreEqual(2010, getDistinct[1], "Wrong year.");


    }
}

This is working fine. But this is not actually what I want to do. Since I have to setup the method Find on the mock object I also need to know how it is going to be called in my logic class. If I would like to do TDD I don't want to mind about this. All I want to know is which Calendar entities my repository should provide. I would like to setup the GetQuery method. Like this:

repositoryMock.Setup(r => r.GetQuery<Calender_Tbl>()).Returns(new List<Calender_Tbl>
{
  new Calender_Tbl
      {
          Date =
              new DateTime(2010, 1, 1),
          Year = 2010
      },
  new Calender_Tbl
      {
          Date =
              new DateTime(2010, 2, 1),
          Year = 2010
      },
  new Calender_Tbl
      {
          Date =
              new DateTime(2011, 1, 1),
          Year = 2011
      }
}.AsQueryable());

So when Find is calling GetQuery internally in the GenericRepository class it should get the correct Calendar entities that I setup in GetQuery. But this is not working of course. Since I haven't setup the Find method of my mock object I don't get any entities.

So what to do? Of course I could probably use Moles or some other framework which mocks everything but I don't want to do that. Is there anything I can do in the design of the class or test to solve the issue?

It is not the end of the world if I have to go with my current solution but what if the property year turns into a not nullable int? Then of course I will have to change my implementation in the logic class but I would also have to change the test. I would like to try to avoid this.

like image 456
John Avatar asked Sep 02 '11 09:09

John


1 Answers

I can see two ways:

public class MockRepository : IRepository
{
    private List<object> entities;
    public MockRepository(params object[] entitites)
    {
      this.entities = entities.ToList();
    }

    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        return this.entities.OfType<TEntity>().AsQueryable();
    }

    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

That's the easiest and my preferred way. Moq isn't the hammer for everything ;)

Alternatively, if you really insist on using Moq (I'm flattered, but it's very much unnecessary in this case, as you can do state based testing on the returned entities), you can do:

public class GenericRepository : IRepository
{
    public virtual IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        var entityName = GetEntityName<TEntity>();
        return Context.CreateQuery<TEntity>(entityName);
    }
    private string GetEntityName<TEntity>() where TEntity : class
    {
        return typeof(TEntity).Name;
    }
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

And then use Moq to override the behavior of GetQuery:

var repository = new Mock<GenericRepository> { CallBase = true };

repository.Setup(x => x.GetQuery<Foo>()).Returns(theFoos.AsQueryable());

What will happen is that the Find method will get executed on the GenericRepository class, which will in turn the GetQuery, which has been overwritten by Moq to provide the fixed set of entities.

I set CallBase = true explicitly just in case you happen to make Find virtual too, so that we ensure it's always called. Not technically needed if the Find isn't virtual, as it will always be invoked on the actual class the mock is inheriting/mocking from.

I'd go for the first option, much simpler to understand what's going on, and it can be reused outside of the context of a single particular test (just pass any entities you need and it will work for everything).

like image 98
kzu Avatar answered Oct 05 '22 23:10

kzu