Click here to Skip to main content
13,139,496 members (62,279 online)
Click here to Skip to main content
Add your own
alternative version

Stats

33K views
70 bookmarked
Posted 11 Feb 2015

To Repository Or NOT

, 15 Feb 2015
Rate this:
Please Sign up or sign in to vote.
A look at how to unit test code that makes use of Repository vs using raw Entity Framework, where we will do this using mocks/test doubles

Introduction

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.

 

An Apology

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.

 

 

Where Is The Code?

The code is avaiable on my github account : https://github.com/sachabarber/TestingEntityFramework

 

Prerequisites

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

 

Testing Using Repositories

  • 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.

 

The System Under Test (SUT) Using Repositories

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:

//Sync
void Insert(string url);
IEnumerable<Post> GetAll();
IEnumerable<Post> GetAll(Expression<Func<Post, bool>> filter);
Post FindById(int id);

//Async
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

 

The Repository

I tend to have this sort of repository. There are a couple of things to note here:

  1. 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>)
  2. It can be used with a Unit Of Work abstraction
  3. 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>();
    }

    /// <summary>
    /// Used for Lazyloading navigation properties
    /// </summary>
    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>());
    }

    /// <summary>
    /// Used for Lazyloading navigation properties
    /// </summary>
    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
}

 

The UOW Abstraction

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()
    {
        //Not done for this example
    }
}

 

IOC Wire Up Using Repositories

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();

        // Register individual components
        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;
        }
    }


}

The Tests Using Repositories

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

Insert( ) Using Repositories

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());
}

GetAll( ) Using Repositories

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);
}

 

GetAll( ) Where We Provide An Expression<Func<Post,bool>> Filter, Using Repositories

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);
}

FindById( ) Using Repositories

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:

 

 

Testing Using Entity Framework

  • 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)

LazyLoading

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.

 

The System Under Test (SUT)

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)
    {
        //NOTE : Even if you included a line like the one below it would include 
        //the PostComments, which seems to be NonLazy
        //this is due to the fact that the Post(s) and Comment(s) are already in the Context
        //var post1 = context.Posts.FirstOrDefault(p => p.Id == id);

        //This should show that we are not doing Lazy Loading and DO NEED to use 
        //Include for navigation properties
        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)
    {
        //NOTE : Even if you included a line like the one below it would include 
        //the PostComments, which seems to be NonLazy
        //this is due to the fact that the Post(s) and Comment(s) are already in the Context
        //var post1 = context.Posts.FirstOrDefault(p => p.Id == id);

        //This should show that we are not doing Lazy Loading and DO NEED to use 
        //Include for navigation properties
        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();
    }
}

IOC Wire Up

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();
 
        // Register individual components
        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;
        }
    }
}

The Tests

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

 

DbContext Test Double

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()
}

 

Async Versions

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; }
    }
}

 

 

 

Insert( )

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

GetAll( )

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);
}

 

GetAll( ) Where We Provide An Expression<Func<Post,bool>> Filter

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());
}

FindById( )

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)
{
    //NOTE : Even if you included a line like the one below it would include 
    //the PostComments, which seems to be NonLazy
    //this is due to the fact that the Post(s) and Comment(s) are already in the Context
    //var post1 = context.Posts.FirstOrDefault(p => p.Id == id);

    //This should show that we are not doing Lazy Loading and DO NEED to use 
    //Include for navigation properties
    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)
{
    //NOTE : Even if you included a line like the one below it would include 
    //the PostComments, which seems to be NonLazy
    //this is due to the fact that the Post(s) and Comment(s) are already in the Context
    //var post1 = context.Posts.FirstOrDefault(p => p.Id == id);

    //This should show that we are not doing Lazy Loading and DO NEED to use 
    //Include for navigation properties
    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);

    //NOTE : we need to use the string version of Include as the other one that accepts
    //       an Expression tree is an extension method in System.Data.Entity.QueryableExtensions
    //       which Moq doesn't like
    //
    // So the following will not work, as will result in this sort of Exception from Moq
    //
    //       Expression references a method that does not belong to 
    //       the mocked object: m => m.Include<Post,IEnumerable`1>(It.IsAny<Expression`1>())
    //
    // dbsetMock.Setup(m => m.Include(It.IsAny<Expression<Func<Post,IEnumerable<PostComment>>>>()))
    //       .Returns(dbsetMock.Object);
    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());


    //NOTE : we need to use the string version of Include as the other one that accepts
    //       an Expression tree is an extension method in System.Data.Entity.QueryableExtensions
    //       which Moq doesn't like
    //
    // So the following will not work, as will result in this sort of Exception from Moq
    //
    //       Expression references a method that does not belong to 
    //       the mocked object: m => m.Include<Post,IEnumerable`1>(It.IsAny<Expression`1>())
    //
    // dbsetMock.Setup(m => m.Include(It.IsAny<Expression<Func<Post,IEnumerable<PostComment>>>>()))
    //       .Returns(dbsetMock.Object);
    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:

 

Conclusion

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.

 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

You may also be interested in...

Comments and Discussions

 
QuestionTests inserting data and fetching from DB Pin
Ehsan Sajjad29-Apr-16 8:49
professionalEhsan Sajjad29-Apr-16 8:49 
AnswerRe: Tests inserting data and fetching from DB Pin
Sacha Barber29-Apr-16 9:28
mvpSacha Barber29-Apr-16 9:28 
QuestionApplication_Start Pin
nrg_rh1-Aug-15 22:18
membernrg_rh1-Aug-15 22:18 
SuggestionJust beware LINQ-to-Objects vs LINQ-to-SQL Pin
Davyd McColl16-Feb-15 22:07
memberDavyd McColl16-Feb-15 22:07 
GeneralRe: Just beware LINQ-to-Objects vs LINQ-to-SQL Pin
Sacha Barber16-Feb-15 22:44
mvpSacha Barber16-Feb-15 22:44 
QuestionJust beware LINQ-to-Objects vs LINQ-to-SQL Pin
Davyd McColl16-Feb-15 22:10
memberDavyd McColl16-Feb-15 22:10 
AnswerRe: Just beware LINQ-to-Objects vs LINQ-to-SQL Pin
Sacha Barber16-Feb-15 22:43
mvpSacha Barber16-Feb-15 22:43 
GeneralRe: Just beware LINQ-to-Objects vs LINQ-to-SQL Pin
Davyd McColl16-Feb-15 22:53
memberDavyd McColl16-Feb-15 22:53 
GeneralRe: Just beware LINQ-to-Objects vs LINQ-to-SQL Pin
Sacha Barber16-Feb-15 23:16
mvpSacha Barber16-Feb-15 23:16 
GeneralMy vote of 5 Pin
fredatcodeproject16-Feb-15 2:26
memberfredatcodeproject16-Feb-15 2:26 
GeneralRe: My vote of 5 Pin
Sacha Barber16-Feb-15 3:15
mvpSacha Barber16-Feb-15 3:15 
QuestionAyende Pin
Simon_Whale15-Feb-15 22:40
professionalSimon_Whale15-Feb-15 22:40 
AnswerRe: Ayende Pin
Sacha Barber15-Feb-15 22:49
mvpSacha Barber15-Feb-15 22:49 
QuestionBenefit Pin
Member 777543912-Feb-15 0:35
memberMember 777543912-Feb-15 0:35 
AnswerRe: Benefit Pin
Sacha Barber12-Feb-15 1:54
mvpSacha Barber12-Feb-15 1:54 
GeneralRe: Benefit Pin
David Osborne12-Feb-15 6:22
memberDavid Osborne12-Feb-15 6:22 
GeneralRe: Benefit Pin
Sacha Barber12-Feb-15 7:00
mvpSacha Barber12-Feb-15 7:00 
GeneralNice one sir! Pin
Steve Solomon11-Feb-15 21:44
memberSteve Solomon11-Feb-15 21:44 
GeneralRe: Nice one sir! Pin
Sacha Barber11-Feb-15 23:52
mvpSacha Barber11-Feb-15 23:52 
GeneralRe: Nice one sir! Pin
Steve Solomon12-Feb-15 1:56
memberSteve Solomon12-Feb-15 1:56 
GeneralRe: Nice one sir! Pin
Sacha Barber12-Feb-15 1:57
mvpSacha Barber12-Feb-15 1:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.170915.1 | Last Updated 16 Feb 2015
Article Copyright 2015 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid