When writing tests for your application it is often desirable to avoid hitting the database.  Entity Framework allows you to achieve this by creating a context – with behavior defined by your tests – that makes use of in-memory data.

This article will cover the following topics:

Options for creating test doubles

There are two different approaches that can be used to create an in-memory version of your context.

  • Create your own test doubles – This approach involves writing your own in-memory implementation of your context and DbSets. This gives you a lot of control over how the classes behave but can involve writing and owning a reasonable amount of code.
  • Use a mocking framework to create test doubles – Using a mocking framework (such as Moq) you can have the in-memory implementations of you context and sets created dynamically at runtime for you.

This article will deal with using a mocking framework. For creating your own test doubles see Testing with your own test doubles (EF6 onwards).

To demonstrate using EF with a mocking framework we are going to use Moq. The easiest way to get Moq is to install the Moq package from NuGet.

Testing with pre-EF6 versions

The scenario shown in this article is dependent on some changes we made to DbSet in EF6. For testing with EF5 and earlier version see Testing with a Fake Context.

Limitations of EF in-memory test doubles

In-memory test doubles can be a good way to provide unit test level coverage of bits of your application that use EF. However, when doing this you are using LINQ to Objects to execute queries against in-memory data. This can result in different behavior than using EF’s LINQ provider (LINQ to Entities) to translate queries into SQL that is run against your database.

One example of such a difference is loading related data. If you create a series of Blogs that each have related Posts, then when using in-memory data the related Posts will always be loaded for each Blog. However, when running against a database the data will only be loaded if you use the Include method.

For this reason, it is recommended to always include some level of end-to-end testing (in addition to your unit tests) to ensure your application works correctly against a database.

Following along with this article

This article gives complete code listings that you can copy into Visual Studio to follow along if you wish. It's easiest to create a Unit Test Project and you will need to target .NET Framework 4.5 to complete the sections that use async.

The EF model

The service we're going to test makes use of an EF model made up of the BloggingContext and the Blog and Post classes. This code may have been generated by the EF Designer or be a Code First model.

using System.Collections.Generic; 
using System.Data.Entity; 
 
namespace TestingDemo 
{ 
    public class BloggingContext : DbContext 
    { 
        public virtual DbSet<Blog> Blogs { get; set; } 
        public virtual DbSet<Post> Posts { get; set; } 
    } 
 
    public class Blog 
    { 
        public int BlogId { get; set; } 
        public string Name { get; set; } 
        public string Url { get; set; } 
 
        public virtual List<Post> Posts { get; set; } 
    } 
 
    public class Post 
    { 
        public int PostId { get; set; } 
        public string Title { get; set; } 
        public string Content { get; set; } 
 
        public int BlogId { get; set; } 
        public virtual Blog Blog { get; set; } 
    } 
}

Virtual DbSet properties with the EF Designer

Note that the DbSet properties on the context are marked as virtual. This will allow the mocking framework to derive from our context and overriding these properties with a mocked implementation.

If you are using Code First then you can edit your classes directly. If you are using the EF Designer then you’ll need to edit the T4 template that generates your context. Open up the <model_name>.Context.tt file that is nested under you edmx file, find the following fragment of code and add in the virtual keyword as shown.

public string DbSet(EntitySet entitySet) 
{ 
    return string.Format( 
        CultureInfo.InvariantCulture, 
        "{0} virtual DbSet<{1}> {2} {{ get; set; }}", 
        Accessibility.ForReadOnlyProperty(entitySet), 
        _typeMapper.GetTypeName(entitySet.ElementType), 
        _code.Escape(entitySet)); 
}

Service to be tested

To demonstrate testing with in-memory test doubles we are going to be writing a couple of tests for a BlogService. The service is capable of creating new blogs (AddBlog) and returning all Blogs ordered by name (GetAllBlogs). In addition to GetAllBlogs, we’ve also provided a method that will asynchronously get all blogs ordered by name (GetAllBlogsAsync).

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 
using System.Threading.Tasks; 
 
namespace TestingDemo 
{ 
    public class BlogService 
    { 
        private BloggingContext _context; 
 
        public BlogService(BloggingContext context) 
        { 
            _context = context; 
        } 
 
        public Blog AddBlog(string name, string url) 
        { 
            var blog = _context.Blogs.Add(new Blog { Name = name, Url = url }); 
            _context.SaveChanges(); 
 
            return blog; 
        } 
 
        public List<Blog> GetAllBlogs() 
        { 
            var query = from b in _context.Blogs 
                        orderby b.Name 
                        select b; 
 
            return query.ToList(); 
        } 
 
        public async Task<List<Blog>> GetAllBlogsAsync() 
        { 
            var query = from b in _context.Blogs 
                        orderby b.Name 
                        select b; 
 
            return await query.ToListAsync(); 
        } 
    } 
}

Testing non-query scenarios

That’s all we need to do to start testing non-query methods. The following test uses Moq to create a context. It then creates a DbSet<Blog> and wires it up to be returned from the context’s Blogs property. Next, the context is used to create a new BlogService which is then used to create a new blog – using the AddBlog method. Finally, the test verifies that the service added a new Blog and called SaveChanges on the context.

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using Moq; 
using System.Data.Entity; 
 
namespace TestingDemo 
{ 
    [TestClass] 
    public class NonQueryTests 
    { 
        [TestMethod] 
        public void CreateBlog_saves_a_blog_via_context() 
        { 
            var mockSet = new Mock<DbSet<Blog>>(); 
 
            var mockContext = new Mock<BloggingContext>(); 
            mockContext.Setup(m => m.Blogs).Returns(mockSet.Object); 
 
            var service = new BlogService(mockContext.Object); 
            service.AddBlog("ADO.NET Blog", "http://blogs.msdn.com/adonet"); 
 
            mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once()); 
            mockContext.Verify(m => m.SaveChanges(), Times.Once()); 
        } 
    } 
}

Testing query scenarios

In order to be able to execute queries against our DbSet test double we need to setup an implementation of IQueryable. The first step is to create some in-memory data – we’re using a List<Blog>. Next, we create a context and DBSet<Blog> then wire up the IQueryable implementation for the DbSet – they’re just delegating to the LINQ to Objects provider that works with List<T>.

We can then create a BlogService based on our test doubles and ensure that the data we get back from GetAllBlogs is ordered by name.

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using Moq; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 
 
namespace TestingDemo 
{ 
    [TestClass] 
    public class QueryTests 
    { 
        [TestMethod] 
        public void GetAllBlogs_orders_by_name() 
        { 
            var data = new List<Blog> 
            { 
                new Blog { Name = "BBB" }, 
                new Blog { Name = "ZZZ" }, 
                new Blog { Name = "AAA" }, 
            }.AsQueryable(); 
 
            var mockSet = new Mock<DbSet<Blog>>(); 
            mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
            mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
            mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
            mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator()); 
 
            var mockContext = new Mock<BloggingContext>(); 
            mockContext.Setup(c => c.Blogs).Returns(mockSet.Object); 
 
            var service = new BlogService(mockContext.Object); 
            var blogs = service.GetAllBlogs(); 
 
            Assert.AreEqual(3, blogs.Count); 
            Assert.AreEqual("AAA", blogs[0].Name); 
            Assert.AreEqual("BBB", blogs[1].Name); 
            Assert.AreEqual("ZZZ", blogs[2].Name); 
        } 
    } 
}

Testing with async queries

In order to use asynchronous queries we need to do a little more work. If we tried to use our Moq DbSet with the GetAllBlogsAsync method we would get the following exception:

System.InvalidOperationException: The source IQueryable doesn't implement IDbAsyncEnumerable<TestingDemo.Blog>. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.

In order to use the async methods we need to create an in-memory DbAsyncQueryProvider to process the async query. Whilst it would be possible to setup a query provider using Moq, it is much easier to create a test double implementation in code. The code for this implementation is as follows:

using System.Collections.Generic; 
using System.Data.Entity.Infrastructure; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Threading; 
using System.Threading.Tasks; 
 
namespace TestingDemo 
{ 
    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; } 
        } 
    } 
}

Testing with a mocking framework (EF6 onwards)的更多相关文章

  1. Googletest - Google Testing and Mocking Framework

    Googletest - Google Testing and Mocking Framework https://github.com/google/googletest

  2. Working with Transactions (EF6 Onwards)

    Data Developer Center > Learn > Entity Framework > Get Started > Working with Transactio ...

  3. [转]Working with Transactions (EF6 Onwards)

    本文转自:http://www.cnblogs.com/xiepeixing/p/5275999.html Working with Transactions (EF6 Onwards) This d ...

  4. What is a mocking framework? Why is it useful?

    Today I want to talk about mocking frameworks and why they are useful. In order to do that I first n ...

  5. 什么是Mocking framework?它有什么用?

    原位地址:http://codetunnel.com/blog/post/what-is-a-mocking-framework-why-is-it-useful 今天我想讲下关于mocking fr ...

  6. Mocking framework

    [译] 什么是Mocking framework?它有什么用? 原位地址:http://codetunnel.com/blog/post/what-is-a-mocking-framework-why ...

  7. 什么是Mocking framework?它有什么用?(转)

    今天我想讲下关于mocking frameworks,并且解释下他为什么有用处.我将给你们展示用和不用mocking framework两种测试方法. 假设我们已经有了一个Driver类: publi ...

  8. What is Data Driven Testing? Learn to create Framework

    What is Data Driven Testing? Data-driven is a test automation framework which stores test data in a ...

  9. Entity Framework EF6使用 MySql创建数据库异常解决办法

    EF6使用MySQL数据库时,第一次创建数据库出现“Specified key was too long; max key length is 767 bytes”错误,解决办法请见以下连接. htt ...

随机推荐

  1. 阿里巴巴分布式服务框架dubbo学习笔记

    Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的 ...

  2. mahout 安装测试

    1 下载 在http://archive.apache.org/dist/mahout下载相应版本的mahout 版本,获取官网查看http://mahout.apache.org 相关的信息

  3. 使用excel计算指数平滑和移动平均

      指数平滑法 原数数据如下: 点击数据——数据分析 选择指数平滑 最一次平滑 由于我们选择的区域是B1:B22,第一个单元格“钢产量”,被当做标志,所以我们应该勾选标志.当我们勾选了标志后,列中的第 ...

  4. npapi插件开发流程与实例

    近期做NPAPI插件,网上的介绍还是比较多,但就是没有一个完整的例子,FQ也没找到,难得NPAPI要走向陌路了?就不去深究额,先解决目前遇到的问题. 现状:已有Activex(仅兼容IE32/64位浏 ...

  5. LOL 控制技能的解释

    压制:硬控的一种,是指A在释放压制型技能的时候B无法进行任何动作,可以被 水银腰带 和 坩埚盖 解除(原来可以完全解掉,现在貌似只是能动,伤害还在继续,比如蚱蜢的R中,用水银解掉之后可以立即跑开,但是 ...

  6. 利用session_set_save_handler()函数将session保存到MySQL数据库中

    PHP保存session默认的是采用的文件的方式来保存的,这仅仅在文件的空间开销很小的windows上是可以采用的,但是如果我们采用uinx或者是liux上的文件系统的时候,这样的文件系统的文件空间开 ...

  7. Linux上进行单片机开发

    linux上可以使用sdcc进行单片机开发 ubuntu使用 apt-get install sdcc 即可安装. 附一个比较通用的Makefile PRJ := test SRC := $(wild ...

  8. HDU 3966 Aragorn's Story 树链剖分

    Link: http://acm.hdu.edu.cn/showproblem.php?pid=3966 这题注意要手动扩栈. 这题我交g++无限RE,即使手动扩栈了,但交C++就过了. #pragm ...

  9. 兼容IE8以下浏览器input表单属性placeholder不能智能提示功能

    当前很多表单提示使用了表单属性placeholder,可这属性不兼容IE8以下的浏览器,我自己写了一个兼容处理js // 兼容IE8以下浏览器input不能智能提示功能 if(navigator.ap ...

  10. js操作数组的一些小技巧

    1.从数组中随机获取成员 var items = [12, 548 , 'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' , 2145 , 119]; var  rando ...