Reputation: 6516
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
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