Abe
Abe

Reputation: 6516

How do I Mock a EF Code First Repository given this interface?

I am doing a project at work and part of my goal is to learn and apply a test-drive development approach.

I am designing a business class that works with a EF code first repository, but I would like to create a mock of the repository rather than actually hit the database.

Given the following repository interface how can I accomplish this with a mocking framework like MOQ? The challenge is how do I moq the Find method which lets you include other entities?

public interface IRepository<T> where T : EntityBase, new()
    {
        ICollection<T> Find(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] fetchSelectors);
        ICollection<T> Find(Expression<Func<T, bool>> predicate, int pageNumber, int size, Expression<Func<T, object>> orderBy, string sortOrder, out int count, params Expression<Func<T, object>>[] fetchSelectors);
        ICollection<T> FindAll(params Expression<Func<T, object>>[] fetchSelectors);
        ICollection<T> FindAll(int pageNumber, int size, Expression<Func<T, object>> orderBy, string sortOrder, out int count, params Expression<Func<T, object>>[] fetchSelectors);
        void Save(T entity);
        void Delete(T entity);
        T Create();
    }

Here is the implementation of my generic repository:

public class GenericRepository<T> : IRepository<T> where T : EntityBase, new()
    {
        public GenericRepository(IDbContext context)
        {
            Guard.ArgumentNotNull(context, "context");
            this.Context = context;
            this.DbSet = this.Context.CreateDbSet<T>();
        }

        protected IDbContext Context
        {
            get;
            set;
        }

        protected System.Data.Entity.IDbSet<T> DbSet
        {
            get;
            set;
        }

        public virtual ICollection<T> Find(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] fetchSelectors)
        {
            return this.BuildQuery(predicate, fetchSelectors).ToList();
        }

        public virtual ICollection<T> FindAll(params Expression<Func<T, object>>[] fetchSelectors)
        {
            return this.BuildQuery(p => true, fetchSelectors).ToList();
        }

        public virtual void Save(T entity)
        {
            Guard.ArgumentNotNull(entity, "entity");
            this.Context.SaveOrUpdate(entity);
        }

        public virtual void Delete(T entity)
        {
            Guard.ArgumentNotNull(entity, "entity");
            this.DbSet.Remove(entity);
        }

        public T Create()
        {
            return this.DbSet.Create();
        }

        private IQueryable<T> BuildQuery(Expression <Func<T, bool>> predicate, params Expression<Func<T, object>>[] fetchSelectors)
        {
            var query = this.DbSet as IQueryable<T>;
            if (fetchSelectors != null)
            {
                foreach (var fetchSelector in fetchSelectors)
                {
                    query = query.Include(fetchSelector);
                }
            }
            return query.Where(predicate);
        }


        public ICollection<T> Find(Expression<Func<T, bool>> predicate, int pageNumber, int size, Expression<Func<T, object>> orderBy, string sortOrder, out int count, params Expression<Func<T, object>>[] fetchSelectors)
        {
            count = (this.DbSet as IQueryable<T>).Count(predicate);

            if (size < 1 || size > count)
            {
                throw new ArgumentOutOfRangeException("size");
            }

            var maxPageNumber = (count + size - 1) / size;
            if (pageNumber < 1 || pageNumber > maxPageNumber)
            {
                throw new ArgumentOutOfRangeException("pageNumber");
            }
            if (sortOrder != "asc" && sortOrder != "desc")
            {
                throw new ArgumentException("sortOrder");
            }
            var skipCount = (pageNumber - 1) * size;
            var query = BuildQuery(predicate, fetchSelectors);
            query = sortOrder == "asc" ? query.OrderBy(orderBy) : query.OrderByDescending(orderBy);
            return query.Skip(skipCount).Take(size).ToList();
        }

        public ICollection<T> FindAll(int pageNumber, int size, Expression<Func<T, object>> orderBy, string sortOrder, out int count, params Expression<Func<T, object>>[] fetchSelectors)
        {
            return Find(p => true, pageNumber, size, orderBy, sortOrder, out count, fetchSelectors);
        }
    }

Finally, here is IDbContext

public interface IDbContext
    {
        void SaveOrUpdate<T>(T entity) where T : EntityBase;
        IDbSet<TEntity> CreateDbSet<TEntity>() where TEntity: EntityBase;
    }

Upvotes: 0

Views: 1118

Answers (1)

Gene C
Gene C

Reputation: 2030

So lets say that you have a business logic class that has a dependency on IRepository<T>:

public class FooBusinessLogicClass
{
    readonly IRepository<Foo> repository;

    public FooBusinessLogicClass( IRepository<Foo> repository )
    {
        this.repository = repository;
    }

    public ICollection<Foo> FindFoo()
    {
        ...
        var collection = repository.Find( x => x.SomeLambdaExpression(), y => y.SomeOtherExpression() );
        ...

        return collection;
    }
}

If you just want your repository to return some fake data when it is called and you don't care what parameters are passed to it you can use the Moq function It.IsAny<T>():

    public void Verify()
    {
        //Arrange
        var repositoryMock = new Mock<IRepository<Foo>>();
        var example = new FooBusinessLogicClass( repositoryMock.Object );

        //Our fake data for the repository to return
        var expectedResult = new[] { new Foo(), new Foo() };

        //Our setup that ignores the lambda expressions
        repositoryMock.Setup( mock => mock.Find(
                    It.IsAny<Expression<Func<Foo, bool>>>(),
                    It.IsAny<Expression<Func<Foo, object>>[]>() ) )
                .Returns( expectedResult );

        //Act
        var actualResult = example.FindFoo();

        //Assert
        Assert.AreEqual( expectedResult, actualResult );
    }
}

If you do care what the parameters are (let's say you want to verify that Find was called), you just have to include the lambda expressions in your Verify():

        //Assert
        repositoryMock.Verify( mock => mock.Find( 
                x => x.SomeLambdaExpression(), 
                y => y.SomeOtherExpression() ) );

In any case, the details of the implementation of GenericRepository<T> and the interface of IDBContext are irrelevant to the testing of our business logic class. Now, you probably want to write unit tests against your implementation of GenericRepository<T> (using a mock of IDBContext), but that is separate concern.

Upvotes: 1

Related Questions