The short story
This article demonstrates how to unit test code that uses Repositories, or uses Entity Framework directly.
The long story
At work we use a mixture of things to do our datalayer code. This includes raw ADO .NET / Dapper and NHibernate. I have been using NHibernate for around 2 years now, but in the past I have used Entity Framework. Like many others would have done, I have come up with patterns that allow me to test my data access code more easily. This typically means the use of the Repository pattern, which acts as an abstraction over the actual database. Using the repository pattern is great and definately allows you to create mocks/test doubles that allow all your code to be tested without actually relying on a database.
The other thing I like to do with my repository patterns is that I like to pass in a Unit Of Work abstraction such that many repository actions may be grouped together in one commit. In Nhibernate this would be the ISession abstraction. When using the Entity Framework there is no common interface abstraction to represent a Unit Of Work, but we are completely free to roll our own by creating an interface and having our Entity Framework DbContext
implement such an interface.
Anyway we digress, my point is that I have typically used the Repository pattern to make my code more testable. Thing is the other day I came across a post by the Entity Framework team which talks about just using the DbContext
(which is what this article will use) directly in your code, as they have done a bunch of work to make it much more testable. The blog post is by the Entity Framework team and they are talking about the current (at time of writing EF 6) version of Entity Framework, which is what this article is based on.
This is not the first time that I have seen blog posts telling us all to ditch repositories, in fact here are a couple more by Ayende, who is one of the main movers and shakers on NHibernate:
http://ayende.com/blog/3955/repository-is-the-new-singleton
http://ayende.com/blog/4784/architecting-in-the-pit-of-doom-the-evils-of-the-repository-abstraction-layer
Mmmmm interesting.
As I say at work I use NHibernate (where I also use Repositories to aid in my testing, though I dont worry about creating specification classes, why do that when you have IQueryable
and exression trees, and lambdas), but I do have a soft spot for the Entity Framework, so this is what this article uses.
So with all that in mind, I decided to set our to create 2 simple classes that will test out the following:
- 1st class will take a dependancy on a Repository for all its data acess
- 2nd class will use Entity Framework directly
For each of these scenarios there will be some code, and some tests that verify the code works without a database, using a mixture of test doubles and mocks.
As this article is all about testing things, there is not much to say in the text of this article, as it is all really just about presenting the system under test, and then the tests. As such there is a lot of code in this article and not much else, so I apologise for that in advance, hopefully the test code will be useful for some of you.
The code is avaiable on my github account : https://github.com/sachabarber/TestingEntityFramework
In order to run the code associated with this article you will need the following:
- SQL Server Installation
- Run the following 2 setup scripts against a new (or existing) SQL database.
- DB Scripts\Create Posts.sql
- DB Scripts\Create Post Comments.sql
- Ensure that you have changed the App.Config files in the following projects to reflect your SQL server installation
- EFTest.csproj
- EFTest.WithRepositories.csproj
- See : EFTest.WithRepositories.csproj
- See : EFTest.WithRepositories.Tests.csproj
This section talks a out a set of files that make use of the Repository pattern, and also a Unit Of Work abstraction.
This is the class that we will be aiming to test:
public class SomeService : ISomeService, IDisposable
{
private readonly IUnitOfWork context;
private readonly IRepository<Post> repository;
private int counter;
public SomeService(IUnitOfWork context, IRepository<Post> repository)
{
this.context = context;
this.repository = repository;
}
public void Insert(string url)
{
Post post = new Post() { Url = url };
post.PostComments.Add(new PostComment()
{
Comment = string.Format("yada yada {0}", counter++)
});
repository.Add(post);
}
public IEnumerable<Post> GetAll()
{
return repository.GetAll();
}
public IEnumerable<Post> GetAll(Expression<Func<Post, bool>> filter)
{
return repository.GetAll(filter);
}
public Post FindById(int id)
{
var post = repository.Get(id);
return post;
}
public Task<bool> InsertAsync(string url)
{
Post post = new Post() { Url = url };
post.PostComments.Add(new PostComment()
{
Comment = string.Format("yada yada {0}", counter++)
});
return repository.AddAsync(post);
}
public async Task<List<Post>> GetAllAsync()
{
var posts = await repository.GetAllAsync();
return posts.ToList();
}
public Task<Post> FindByIdAsync(int id)
{
return repository.GetIncludingAsync(id, x => x.PostComments);
}
public void Dispose()
{
context.Dispose();
}
}
It can be seen that there are a few things that we need to test there, namely these methods:
void Insert(string url);
IEnumerable<Post> GetAll();
IEnumerable<Post> GetAll(Expression<Func<Post, bool>> filter);
Post FindById(int id);
Task<bool> InsertAsync(string url);
Task<List<Post>> GetAllAsync();
Task<Post> FindByIdAsync(int id);
So now that we know what we are trying to test looks like, lets proceed to look at other moving parts
I tend to have this sort of repository. There are a couple of things to note here:
- It is a generic repository, so can be used against multiple entity types (if you require specialized repositories you may find you have to move away from generic repositories in favour of specific ones that simple inherit from
Repository<T>
) - It can be used with a Unit Of Work abstraction
- It can be used to include
DbContext
navigation properties
public class Repository<T> : IRepository<T> where T : class, IEntity
{
private readonly IUnitOfWork context;
public Repository(IUnitOfWork context)
{
this.context = context;
}
#region Sync
public int Count()
{
return context.Get<T>().Count();
}
public void Add(T item)
{
context.Add(item);
}
public bool Contains(T item)
{
return context.Get<T>().FirstOrDefault(t => t == item) != null;
}
public bool Remove(T item)
{
return context.Remove(item);
}
public T Get(int id)
{
return context.Get<T>().SingleOrDefault(x => x.Id == id);
}
public T GetIncluding(
int id,
params Expression<Func<T, object>>[] includeProperties)
{
return GetAllIncluding(includeProperties).SingleOrDefault(x => x.Id == id);
}
public IQueryable<T> GetAll()
{
return context.Get<T>();
}
public IQueryable<T> GetAll(Expression<Func<T, bool>> predicate)
{
return context.Get<T>().Where(predicate).AsQueryable<T>();
}
public IQueryable<T> GetAllIncluding(
params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> queryable = GetAll();
foreach (Expression<Func<T, object>> includeProperty in includeProperties)
{
queryable = queryable.Include(includeProperty);
}
return queryable;
}
#endregion
#region Async
public async Task<int> CountAsync()
{
return await Task.Run(() => context.Get<T>().Count());
}
public Task<bool> AddAsync(T item)
{
return Task.Run(() =>
{
context.Add(item);
return true;
});
}
public Task<bool> ContainsAsync(T item)
{
return Task.Run(
() => context.Get<T>().FirstOrDefault(t => t == item) != null);
}
public Task<bool> RemoveAsync(T item)
{
return Task.Run(() => context.Remove(item));
}
public Task<T> GetAsync(int id)
{
return Task.Run(
() => context.Get<T>().SingleOrDefault(x => x.Id == id));
}
public async Task<T> GetIncludingAsync(
int id,
params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> queryable = await GetAllIncludingAsync(includeProperties);
return await queryable.SingleOrDefaultAsync(x => x.Id == id);
}
public Task<IQueryable<T>> GetAllAsync()
{
return Task.Run(() => context.Get<T>());
}
public Task<IQueryable<T>> GetAllAsync(
Expression<Func<T, bool>> predicate)
{
return Task.Run(() =>
context.Get<T>().Where(predicate).AsQueryable<T>());
}
public Task<IQueryable<T>> GetAllIncludingAsync(
params Expression<Func<T, object>>[] includeProperties)
{
return Task.Run(
() =>
{
IQueryable<T> queryable = GetAll();
foreach (Expression<Func<T, object>> includeProperty in includeProperties)
{
queryable = queryable.Include(includeProperty);
}
return queryable;
});
}
#endregion
}
As I say the Repository code that I show above relies on a Unit Of Work abstraction. So what is this abstraction exactly. Well put quite simply it is a Entity Framework DbContext
, it is just that we would not use it directly, we would always obtain/insert data using the Respository I showed above. As I have already stated one benefit of having the repositories using the Unit Of Work abstraction is that we can commit several repository actions in one transaction. Anyway here is the code for the Unit Of Work abstraction that this example uses
public abstract class EfDataContextBase : DbContext, IUnitOfWork
{
public EfDataContextBase(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
public IQueryable<T> Get<T>() where T : class
{
return Set<T>();
}
public bool Remove<T>(T item) where T : class
{
try
{
Set<T>().Remove(item);
}
catch (Exception)
{
return false;
}
return true;
}
public new int SaveChanges()
{
return base.SaveChanges();
}
public void Attach<T>(T obj) where T : class
{
Set<T>().Attach(obj);
}
public void Add<T>(T obj) where T : class
{
Set<T>().Add(obj);
}
}
public class RepositoryExampleSachaTestContext : EfDataContextBase, ISachaContext
{
public RepositoryExampleSachaTestContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Post> Posts { get; set; }
public void DoSomethingDirectlyWithDatabase()
{
}
}
In order to wire all this up correctly, I use a IOC container. I have chosen to use Autofac. To be honest the IOC code is kind of incidental and of no interest really, but I shall include it here for completeness:
public class IOCManager
{
private static IOCManager instance;
static IOCManager()
{
instance = new IOCManager();
}
private IOCManager()
{
var builder = new ContainerBuilder();
builder.RegisterType<RepositoryExampleSachaTestContext>()
.As<IUnitOfWork>()
.WithParameter("nameOrConnectionString", "SachaTestContextConnection")
.InstancePerLifetimeScope();
builder.RegisterType<SomeService>()
.As<ISomeService>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(Repository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
Container = builder.Build();
}
public IContainer Container { get; private set; }
public static IOCManager Instance
{
get
{
return instance;
}
}
}
Ok so we have now seen all the parts of the system that we want to test, so lets now look at some test cases. In all of these tests I will be using the moq mocking library
Here is how we might mock out an insert that occurs through the Repository. Obviously if your code relies on the inserted Id, you will need to extend this and perhaps provide a callback to update the Id of the added Post
if that is of interest to your code.
This is the code we are trying to simulate:
public void Insert(string url)
{
Post post = new Post() { Url = url };
post.PostComments.Add(new PostComment()
{
Comment = string.Format("yada yada {0}", counter++)
});
repository.Add(post);
}
public Task<bool> InsertAsync(string url)
{
Post post = new Post() { Url = url };
post.PostComments.Add(new PostComment()
{
Comment = string.Format("yada yada {0}", counter++)
});
return repository.AddAsync(post);
}
You can see both the synchronous and asynchrounous versions of the test code below
[TestCase]
public void TestInsert()
{
Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
Mock<IRepository<Post>> repoMock = new Mock<IRepository<Post>>();
SomeService service = new SomeService(uowMock.Object, repoMock.Object);
service.Insert("TestInsert");
repoMock.Verify(m => m.Add(It.IsAny<Post>()), Times.Once());
}
[TestCase]
public async void TestInsertAsync()
{
Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
Mock<IRepository<Post>> repoMock = new Mock<IRepository<Post>>();
repoMock.Setup(x => x.AddAsync(It.IsAny<Post>())).Returns(Task.FromResult(true));
SomeService service = new SomeService(uowMock.Object, repoMock.Object);
await service.InsertAsync("TestInsertAsync");
repoMock.Verify(m => m.AddAsync(It.IsAny<Post>()), Times.Once());
}
Here is how we might mock out an GetAll()
call that occurs through the Repository. It can be seen that we can simple return some dummy Post
objects.
This is the code we are trying to simulate:
public IEnumerable<Post> GetAll()
{
return repository.GetAll();
}
public async Task<List<Post>> GetAllAsync()
{
var posts = await repository.GetAllAsync();
return posts.ToList();
}
You can see both the synchronous and asynchrounous versions below
[TestCase]
public void TestGetAll()
{
Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
Mock<IRepository<Post>> repoMock = new Mock<IRepository<Post>>();
var posts = Enumerable.Range(0, 5)
.Select(x => new Post()
{
Url = string.Format("www.someurl{0}", x)
}).ToList();
repoMock.Setup(x => x.GetAll()).Returns(posts.AsQueryable());
SomeService service = new SomeService(uowMock.Object, repoMock.Object);
var retrievedPosts = service.GetAll();
repoMock.Verify(m => m.GetAll(), Times.Once());
CollectionAssert.AreEqual(posts, retrievedPosts);
}
[TestCase]
public async void TestGetAllAsync()
{
Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
Mock<IRepository<Post>> repoMock = new Mock<IRepository<Post>>();
var posts = Enumerable.Range(0, 5).Select(x => new Post()
{
Id = x,
Url = string.Format("www.someurl{0}", x)
}).ToList();
repoMock.Setup(x => x.GetAllAsync()).Returns(Task.FromResult(posts.AsQueryable()));
SomeService service = new SomeService(uowMock.Object, repoMock.Object);
var retrievedPosts = await service.GetAllAsync();
repoMock.Verify(m => m.GetAllAsync(), Times.Once());
CollectionAssert.AreEqual(posts, retrievedPosts);
}
Another thing the repository code that I posted allows is the use of Expression<Func<Post,bool>>
to apply a filter to the IQueryable<Post>
.
This is the code we are trying to simulate:
public IEnumerable<Post> GetAll(Expression<Func<Post, bool>> filter)
{
return repository.GetAll(filter);
}
So how can we write test code that does that. It is not that hard actually, all we need to do is get clever with our mocking, and make sure to apply the filter to the dummy objects before we do any assertions, here is how it is done
[TestCase]
public void TestGetAllWithLambda()
{
Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
Mock<IRepository<Post>> repoMock = new Mock<IRepository<Post>>();
var posts = Enumerable.Range(0, 5)
.Select(x => new Post()
{
Url = string.Format("www.someurl{0}", x)
}).ToList();
for (int i = 0; i < posts.Count; i++)
{
posts[i].PostComments.Add(new PostComment()
{
Comment = string.Format("some test comment {0}", i)
});
}
repoMock.Setup(moq => moq.GetAll(It.IsAny<Expression<Func<Post, bool>>>()))
.Returns((Expression<Func<Post, bool>> predicate) =>
posts.Where(predicate.Compile()).AsQueryable());
SomeService service = new SomeService(uowMock.Object, repoMock.Object);
Func<Post, bool> func = (x) => x.Url == "www.someurl1";
Expression<Func<Post, bool>> filter = post => func(post);
var retrievedPosts = service.GetAll(filter);
CollectionAssert.AreEqual(posts.Where(func), retrievedPosts);
}
Here is how we might mock out an FindById()
call that occurs through the Repository. It can be seen that we can simple return a dummy Post
object.
This is the code we are trying to simulate:
public Post FindById(int id)
{
var post = repository.Get(id);
return post;
}
public Task<Post> FindByIdAsync(int id)
{
return repository.GetIncludingAsync(id, x => x.PostComments);
}
You can see both the synchronous and asynchrounous versions below
[TestCase]
public void TestFindById()
{
Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
Mock<IRepository<Post>> repoMock = new Mock<IRepository<Post>>();
var posts = Enumerable.Range(0, 5).Select(x => new Post()
{
Id = x,
Url = string.Format("www.someurl{0}", x)
}).ToList();
for (int i = 0; i < posts.Count; i++)
{
posts[i].PostComments.Add(new PostComment()
{
Comment = string.Format("some test comment {0}", i)
});
}
repoMock.Setup(moq => moq.Get(It.IsInRange(0, 5, Range.Inclusive)))
.Returns((int id) => posts.SingleOrDefault(x => x.Id == id));
SomeService service = new SomeService(uowMock.Object, repoMock.Object);
var retrievedPost = service.FindById(2);
Assert.AreEqual(2, retrievedPost.Id);
}
[TestCase]
public async void TestFindByIdAsync()
{
Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
Mock<IRepository<Post>> repoMock = new Mock<IRepository<Post>>();
var posts = Enumerable.Range(0, 5).Select(x => new Post()
{
Id = x,
Url = string.Format("www.someurl{0}", x)
}).ToList();
for (int i = 0; i < posts.Count; i++)
{
posts[i].PostComments.Add(new PostComment()
{
Comment = string.Format("some test comment {0}", i)
});
}
repoMock.Setup(moq => moq.GetIncludingAsync(
It.IsInRange(0, 5, Range.Inclusive),
new[] { It.IsAny<Expression<Func<Post, object>>>() }))
.Returns(
(int id, Expression<Func<Post, object>>[] includes) =>
Task.FromResult(posts.SingleOrDefault(x => x.Id == id)));
SomeService service = new SomeService(uowMock.Object, repoMock.Object);
var retrievedPost = await service.FindByIdAsync(2);
Assert.AreEqual(2, retrievedPost.Id);
}
Here is the proof that this all works fine:
- See : EFTest.csproj
- See : EFTest.Tests.csproj
Ok we have seen that we can indeed use a repository to make testing our data access code easier. However like I say the Entity Framework team have released a blog post (https://msdn.microsoft.com/en-us/data/dn314429) that claims to allow us to use Entity Framework DbContext
directly in our code, and still easily use mocks / test doubles. Naturually I wanted to try this, so here we go:
This section talks about using a DbContext
abstraction (You still want to use an abstraction such that any of those nasty direct DbContext.Database
calls can also be mocked and tested correctly)
The Entity Framework allows you to turn off lazy loading. When you do this it is up to you to Include
the navigation properties yourself. The actual code contains lazy load/Non lazy load examples, however for brevity I have chosen to only cover the Non lazy load version for the testing, as I think this is more challenging from a testing perspective as you need to manage the navigation properties, so it is more interesting shall we say.
This is the class that we will be aiming to test:
public class SomeService : ISomeService, IDisposable
{
private readonly ISachaContext context;
private int counter;
public SomeService(ISachaContext context)
{
this.context = context;
}
public void Insert(string url)
{
Post post = new Post() { Url = url };
post.PostComments.Add(new PostComment()
{
Comment = string.Format("yada yada {0}", counter++)
});
context.Posts.Add(post);
}
public IEnumerable<Post> GetAll()
{
return context.Posts.AsEnumerable();
}
public IEnumerable<Post> GetAll(Expression<Func<Post, bool>> filter)
{
return context.Posts.Where(filter).AsEnumerable();
}
public Post FindById(int id)
{
var postWithNoCommentsProof = context.Posts.FirstOrDefault();
var postWithCommentsThanksToInclude = context.Posts
.Include(x => x.PostComments).FirstOrDefault();
var post = context.Posts.Where(p => p.Id == id)
.Include(x => x.PostComments).FirstOrDefault();
return post;
}
public async Task<bool> InsertAsync(string url)
{
Post post = new Post() { Url = url };
post.PostComments.Add(new PostComment()
{
Comment = string.Format("yada yada {0}", counter++)
});
context.Posts.Add(post);
return true;
}
public async Task<List<Post>> GetAllAsync()
{
return await context.Posts.ToListAsync();
}
public async Task<Post> FindByIdAsync(int id)
{
var postWithNoCommentsProof = await context.Posts.FirstOrDefaultAsync();
var postWithCommentsThanksToInclude = await context.Posts
.Include(x => x.PostComments).FirstOrDefaultAsync();
var post = await context.Posts.Where(p => p.Id == id)
.Include(x => x.PostComments).FirstOrDefaultAsync();
return post;
}
public void Dispose()
{
context.Dispose();
}
}
In order to wire all this up correctly, I use a IOC container. I have chosen to use Autofac. To be honest the IOC code is kind of incidental and of no interest really, but I shall include it here for completeness:
public class IOCManager
{
private static IOCManager instance;
static IOCManager()
{
instance = new IOCManager();
}
private IOCManager()
{
var builder = new ContainerBuilder();
builder.RegisterType<SachaContext>()
.As<ISachaContext>()
.WithParameter("nameOrConnectionString", "SachaTestContextConnection")
.InstancePerLifetimeScope();
builder.RegisterType<SachaLazyContext>()
.As<ISachaLazyContext>()
.WithParameter("nameOrConnectionString", "SachaTestContextConnection")
.InstancePerLifetimeScope();
builder.RegisterType<SomeService>()
.As<ISomeService>().InstancePerLifetimeScope();
builder.RegisterType<SomeServiceLazy>()
.As<ISomeServiceLazy>().InstancePerLifetimeScope();
Container = builder.Build();
}
public IContainer Container { get; private set; }
public static IOCManager Instance
{
get
{
return instance;
}
}
}
Ok so we have now seen all the parts of the system that we want to test, so lets now look at some test cases. In all of these tests I will be using the moq mocking library
In order to try out the advise offered on the Entity Framework blog ,we need to ensure that we use a test double for the DbContext
such that we can provide mocked/test double DbSet(s) for it. Here is the one that this article uses:
public class SachaContextTestDouble : DbContext, ISachaContext
{
public virtual DbSet<Post> Posts { get; set; }
public void DoSomethingDirectlyWithDatabase()
}
The async versions of the direct Entity Framework code shown below make use of some helper classes as detailed at the Entity Framework blog https://msdn.microsoft.com/en-us/data/dn314429, which I am showing below for completeness:
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace EFTest.Tests
{
internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal TestDbAsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
return new TestDbAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestDbAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public TestDbAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ }
public TestDbAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
IQueryProvider IQueryable.Provider
{
get { return new TestDbAsyncQueryProvider<T>(this); }
}
}
internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public TestDbAsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
public T Current
{
get { return _inner.Current; }
}
object IDbAsyncEnumerator.Current
{
get { return Current; }
}
}
Here is how we might mock out an insert that occurs through direct Entity Framework usage. Obviously if your code relies on the inserted Id, you will need to extend this and perhaps provide a callback to update the Id of the added Post
if that is of interest to your code.
This is the code we are trying to simulate:
public void Insert(string url)
{
Post post = new Post() { Url = url };
post.PostComments.Add(new PostComment()
{
Comment = string.Format("yada yada {0}", counter++)
});
context.Posts.Add(post);
}
public async Task<bool> InsertAsync(string url)
{
Post post = new Post() { Url = url };
post.PostComments.Add(new PostComment()
{
Comment = string.Format("yada yada {0}", counter++)
});
context.Posts.Add(post);
return true;
}
You can see both the synchronous and asynchrounous versions below
private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> dataForDbSet) where T : class
{
var dbsetMock = new Mock<DbSet<T>>();
dbsetMock.As<IQueryable<T>>().Setup(m => m.Provider)
.Returns(dataForDbSet.Provider);
dbsetMock.As<IQueryable<T>>().Setup(m => m.Expression)
.Returns(dataForDbSet.Expression);
dbsetMock.As<IQueryable<T>>().Setup(m => m.ElementType)
.Returns(dataForDbSet.ElementType);
dbsetMock.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
.Returns(dataForDbSet.GetEnumerator());
return dbsetMock;
}
[TestCase]
public void TestInsert()
{
var dbsetMock = new Mock<DbSet<Post>>();
var uowMock = new Mock<SachaContextTestDouble>();
uowMock.Setup(m => m.Posts).Returns(dbsetMock.Object);
var service = new SomeService(uowMock.Object);
service.Insert("Some url");
dbsetMock.Verify(m => m.Add(It.IsAny<Post>()), Times.Once());
}
NOTE : It can be see for the Entity Framework mocking we need to create a mock DbSet
, this is something that you will see used in all of the examples here. We use a little trick where we use the standard LINQ to objects expression tree and LINQ provider
Here is how we might mock out an GetAll()
call that occurs through direct Entity Framework usage. It can be seen that we can simple return some dummy Post
objects.
This is the code we are trying to simulate:
public IEnumerable<Post> GetAll()
{
return context.Posts.AsEnumerable();
}
public async Task<List<Post>> GetAllAsync()
{
return await context.Posts.ToListAsync();
}
You can see both the synchronous and asynchrounous versions below
private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> dataForDbSet) where T : class
{
var dbsetMock = new Mock<DbSet<T>>();
dbsetMock.As<IQueryable<T>>().Setup(m => m.Provider)
.Returns(dataForDbSet.Provider);
dbsetMock.As<IQueryable<T>>().Setup(m => m.Expression)
.Returns(dataForDbSet.Expression);
dbsetMock.As<IQueryable<T>>().Setup(m => m.ElementType)
.Returns(dataForDbSet.ElementType);
dbsetMock.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
.Returns(dataForDbSet.GetEnumerator());
return dbsetMock;
}
[TestCase]
public void TestGetAll()
{
var posts = Enumerable.Range(0, 5).Select(
x => new Post()
{
Url = string.Format("www.someurl{0}", x)
}).AsQueryable();
var dbsetMock = CreateMockSet(posts);
var mockContext = new Mock<SachaContextTestDouble>();
mockContext.Setup(c => c.Posts).Returns(dbsetMock.Object);
var service = new SomeService(mockContext.Object);
var retrievedPosts = service.GetAll().ToList();
var postsList = posts.ToList();
Assert.AreEqual(posts.Count(), retrievedPosts.Count());
Assert.AreEqual(postsList[0].Url, retrievedPosts[0].Url);
Assert.AreEqual(postsList[4].Url, retrievedPosts[4].Url);
}
[TestCase]
public async Task TestGetAllAsync()
{
var posts = Enumerable.Range(0, 5).Select(
x => new Post()
{
Url = string.Format("www.someurl{0}", x)
}).AsQueryable();
var dbsetMock = new Mock<DbSet<Post>>();
dbsetMock.As<IDbAsyncEnumerable<Post>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<Post>(posts.GetEnumerator()));
dbsetMock.As<IQueryable<Post>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<Post>(posts.Provider));
dbsetMock.As<IQueryable<Post>>().Setup(m => m.Expression).Returns(posts.Expression);
dbsetMock.As<IQueryable<Post>>().Setup(m => m.ElementType).Returns(posts.ElementType);
dbsetMock.As<IQueryable<Post>>().Setup(m => m.GetEnumerator()).Returns(posts.GetEnumerator());
var mockContext = new Mock<SachaContextTestDouble>();
mockContext.Setup(c => c.Posts).Returns(dbsetMock.Object);
var service = new SomeService(mockContext.Object);
var retrievedPosts = await service.GetAllAsync();
var postsList = posts.ToList();
Assert.AreEqual(posts.Count(), retrievedPosts.Count());
Assert.AreEqual(postsList[0].Url, retrievedPosts[0].Url);
Assert.AreEqual(postsList[4].Url, retrievedPosts[4].Url);
}
We can also make use of Expression<Func<Post,bool>>
to apply a filter to the IQueryable<Post>
.
This is the code we are trying to simulate:
public IEnumerable<Post> GetAll(Expression<Func<Post, bool>> filter)
{
return context.Posts.Where(filter).AsEnumerable();
}
So how can we write test code that does that. It is not that hard actually, all we need to do is get clever with our mocking, and make sure to apply the filter to the dummy objects before we do any assertions, here is how it is done
private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> dataForDbSet) where T : class
{
var dbsetMock = new Mock<DbSet<T>>();
dbsetMock.As<IQueryable<T>>().Setup(m => m.Provider)
.Returns(dataForDbSet.Provider);
dbsetMock.As<IQueryable<T>>().Setup(m => m.Expression)
.Returns(dataForDbSet.Expression);
dbsetMock.As<IQueryable<T>>().Setup(m => m.ElementType)
.Returns(dataForDbSet.ElementType);
dbsetMock.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
.Returns(dataForDbSet.GetEnumerator());
return dbsetMock;
}
[TestCase]
public void TestGetAllWithLambda()
{
var posts = Enumerable.Range(0, 5).Select(x => new Post()
{
Url = string.Format("www.someurl{0}", x)
}).ToList();
for (int i = 0; i < posts.Count; i++)
{
posts[i].PostComments.Add(new PostComment()
{
Comment = string.Format("some test comment {0}", i)
});
}
var queryablePosts = posts.AsQueryable();
var dbsetMock = CreateMockSet(queryablePosts);
var mockContext = new Mock<SachaContextTestDouble>();
mockContext.Setup(c => c.Posts).Returns(dbsetMock.Object);
var service = new SomeService(mockContext.Object);
Func<Post, bool> func = (x) => x.Url == "www.someurl1";
Expression<Func<Post, bool>> filter = post => func(post);
var retrievedPosts = service.GetAll(filter);
CollectionAssert.AreEqual(posts.Where(func).ToList(), retrievedPosts.ToList());
}
Here is how we might mock out an FindById()
call that occurs through the direct Entity Framework usage. It can be seen that we can simple return a dummy Post
object.
This is the code we are trying to simulate:
public Post FindById(int id)
{
var postWithNoCommentsProof = context.Posts.FirstOrDefault();
var postWithCommentsThanksToInclude = context.Posts
.Include(x => x.PostComments).FirstOrDefault();
var post = context.Posts.Where(p => p.Id == id)
.Include(x => x.PostComments).FirstOrDefault();
return post;
}
public async Task<Post> FindByIdAsync(int id)
{
var postWithNoCommentsProof = await context.Posts.FirstOrDefaultAsync();
var postWithCommentsThanksToInclude = await context.Posts
.Include(x => x.PostComments).FirstOrDefaultAsync();
var post = await context.Posts.Where(p => p.Id == id)
.Include(x => x.PostComments).FirstOrDefaultAsync();
return post;
}
You can see both the synchronous and asynchrounous versions below
private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> dataForDbSet) where T : class
{
var dbsetMock = new Mock<DbSet<T>>();
dbsetMock.As<IQueryable<T>>().Setup(m => m.Provider)
.Returns(dataForDbSet.Provider);
dbsetMock.As<IQueryable<T>>().Setup(m => m.Expression)
.Returns(dataForDbSet.Expression);
dbsetMock.As<IQueryable<T>>().Setup(m => m.ElementType)
.Returns(dataForDbSet.ElementType);
dbsetMock.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
.Returns(dataForDbSet.GetEnumerator());
return dbsetMock;
}
[TestCase]
public void TestFindById()
{
var posts = Enumerable.Range(0, 5).Select(x => new Post()
{
Id = x,
Url = string.Format("www.someurl{0}", x)
}).ToList();
for (int i = 0; i < posts.Count; i++)
{
posts[i].PostComments.Add(new PostComment()
{
Comment = string.Format("some test comment {0}", i)
});
}
var queryablePosts = posts.AsQueryable();
var dbsetMock = CreateMockSet(queryablePosts);
dbsetMock.Setup(m => m.Include("PostComments")).Returns(dbsetMock.Object);
var mockContext = new Mock<SachaContextTestDouble>();
mockContext.Setup(c => c.Posts).Returns(dbsetMock.Object);
var service = new SomeService(mockContext.Object);
var retrievedPost = service.FindById(1);
Assert.AreEqual(retrievedPost.Id,1);
Assert.IsNotNull(retrievedPost.PostComments);
Assert.AreEqual(retrievedPost.PostComments.Count,1);
}
[TestCase]
public async Task TestFindByIdAsync()
{
var posts = Enumerable.Range(0, 5).Select(x => new Post()
{
Id = x,
Url = string.Format("www.someurl{0}", x)
}).ToList();
for (int i = 0; i < posts.Count; i++)
{
posts[i].PostComments.Add(new PostComment()
{
Comment = string.Format("some test comment {0}", i)
});
}
var queryablePosts = posts.AsQueryable();
var dbsetMock = new Mock<DbSet<Post>>();
dbsetMock.As<IDbAsyncEnumerable<Post>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<Post>(queryablePosts.GetEnumerator()));
dbsetMock.As<IQueryable<Post>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<Post>(queryablePosts.Provider));
dbsetMock.As<IQueryable<Post>>().Setup(m => m.Expression).Returns(queryablePosts.Expression);
dbsetMock.As<IQueryable<Post>>().Setup(m => m.ElementType).Returns(queryablePosts.ElementType);
dbsetMock.As<IQueryable<Post>>().Setup(m => m.GetEnumerator()).Returns(queryablePosts.GetEnumerator());
dbsetMock.Setup(m => m.Include("PostComments")).Returns(dbsetMock.Object);
var mockContext = new Mock<SachaContextTestDouble>();
mockContext.Setup(c => c.Posts).Returns(dbsetMock.Object);
var service = new SomeService(mockContext.Object);
var retrievedPost = await service.FindByIdAsync(1);
Assert.AreEqual(retrievedPost.Id, 1);
Assert.IsNotNull(retrievedPost.PostComments);
Assert.AreEqual(retrievedPost.PostComments.Count, 1);
}
Here is the proof that this all works fine:
So there you have it, it can be seen that these days it is indeed quite possible to use Entity Framework directly. I hope that this article has been of some use to you, and may aid you in testing your own data access layers. The final decision of whether to use Repositories or not is unfortunately not my decision you will have to decide that for yourselves, bue hopefully this has shed some light on how to do it should you want to, which ever way you choose to go.