单元测试 mock EF 中DbContext 和DbSet Include

现在EF越来越流行了,很多时候业务成都是直接访问DbContext 和DbSet来操作数据的。 那么我们测试的时候如何来mock这2个对象了?现在时间很晚了, 就直接贴code吧

首先看看的我们DbContext的类吧:

 public class BloggerEntities : DbContext
    {
        public BloggerEntities()
            : base("BloggerEntities")
        {
            Configuration.ProxyCreationEnabled = false;
        }

        public virtual DbSet<Blog> Blogs { get; set; }
        public virtual DbSet<Article> Articles { get; set; }

       

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new ArticleConfiguration());
            modelBuilder.Configurations.Add(new BlogConfiguration());
        }
    }

public virtual DbSet<Blog> Blogs { get; set; }
public virtual DbSet<Article> Articles { get; set; }

注意了,我一般DbSet属性是没有添加virtual, 结果上面的 mockedContext.Setup(lambdaExpression).Returns(method.Invoke(null, new[] { listForFakeTable }));这句一直报错,搞了我2个小时都没有搞定。不能mock 实例方法。

单元测试code:

 static void Main(string[] args)
        {
            var context = EntityFrameworkMockHelper.GetMockContext<BloggerEntities>().Object;
            context.Articles.Add(new Article
            {
                Author = "Gavin",
                BlogID = 1,
                Contents = "test",
                ID = 2,
                Title = "test title",
                URL = "article URL"
            });
            List<Blog> blogs = new List<Blog> {
            new Blog
            {
                ID = 1,
                URL = "blog url",
                Name = "blogs name"
            },
            new Blog
            {
                ID = 1,
                URL = "blog url",
                Name = "blogs name2222"
            }
             };
            context.Articles.First().Blog = blogs[0];
          
           //add
           context.Blogs.AddRange(blogs);
           //query
           var query1 = (from a in context.Articles
                         join b in context.Blogs on a.BlogID equals b.ID
                         select new { Author = a.Author, BlogName = b.Name }).ToList();
           //remove
           var blog = context.Blogs.FirstOrDefault(x => x.Name == "blogs name2222");
           context.Blogs.Remove(blog);
           //update
           context.Articles.FirstOrDefault(x=>x.ID==2).URL = "updated url";
           var query2 = (from a in context.Articles
                         join b in context.Blogs on a.BlogID equals b.ID
                         select new { Author = a.Author, ArticleUrl = a.URL }).ToList();

            EFService service = new EFService(context);
            var includetest1 = service.GetArticles();
            var includetest2 = service.GetArticles2();
            var includetest3 = service.GetArticles3();
        }

  注意为了 测试include 这里我单独写了一个 service

  public class EFService
    {
        BloggerEntities DBContext { set; get; }
        public EFService(BloggerEntities ctx)
        {
            DBContext = ctx;
        }
        public List<Article> GetArticles()
        {
            return DBContext.Articles.Include("Blog").ToList(); 
        }
        public List<Article> GetArticles2()
        {
            return DBContext.Articles.Include(x=>x.Blog).ToList();
        }
        public List<Article> GetArticles3()
        {
            return (from a in DBContext.Articles select a).Include(x => x.Blog).ToList();
        }
    }

 

我这里的EFService 和BloggerEntities在同一个 程序集里面, 实际上我们应该分开的。

EntityFrameworkMockHelper 的实现如下:

  public class MockedDbContext<T> : Mock<T> where T : DbContext
    {
        public Dictionary<string, object> Tables
        {
            get { return _Tables ?? (_Tables = new Dictionary<string, object>()); }
        }
        private Dictionary<string, object> _Tables;

    }
    public static class EntityFrameworkMockHelper
    {
        /// <summary>
        /// Returns a mock of a DbContext
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static MockedDbContext<T> GetMockContext<T>() where T : DbContext
        {
            var instance = new MockedDbContext<T>();
            instance.MockTables();
            return instance;
        }

        /// <summary>
        /// Use this method to mock a table, which is a DbSet{T} oject, in Entity Framework.
        /// Leave the second list null if no adds or deletes are used.
        /// </summary>
        /// <typeparam name="T">The table data type</typeparam>
        /// <param name="table">A List{T} that is being use to replace a database table.</param>
        /// <returns></returns>
        public static DbSet<T> MockDbSet<T>(List<T> table) where T : class
        {
            var dbSet = new Mock<DbSet<T>>();
            dbSet.As<IQueryable<T>>().Setup(q => q.Provider).Returns(() => table.AsQueryable().Provider);
            dbSet.As<IQueryable<T>>().Setup(q => q.Expression).Returns(() => table.AsQueryable().Expression);
            dbSet.As<IQueryable<T>>().Setup(q => q.ElementType).Returns(() => table.AsQueryable().ElementType);
            dbSet.As<IQueryable<T>>().Setup(q => q.GetEnumerator()).Returns(() => table.AsQueryable().GetEnumerator());
            dbSet.Setup(set => set.Add(It.IsAny<T>())).Callback<T>(table.Add);
            dbSet.Setup(set => set.AddRange(It.IsAny<IEnumerable<T>>())).Callback<IEnumerable<T>>(table.AddRange);
            dbSet.Setup(set => set.Remove(It.IsAny<T>())).Callback<T>(t => table.Remove(t));
            dbSet.Setup(set => set.RemoveRange(It.IsAny<IEnumerable<T>>())).Callback<IEnumerable<T>>(ts =>
            {
                foreach (var t in ts) { table.Remove(t); }
            });
            dbSet.Setup(set => set.Include(It.IsAny<string>())).Returns(dbSet.Object);
            return dbSet.Object;
        }

        /// <summary>
        /// Mocks all the DbSet{T} properties that represent tables in a DbContext.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="mockedContext"></param>
        public static void MockTables<T>(this MockedDbContext<T> mockedContext) where T : DbContext
        {
            Type contextType = typeof(T);
            var dbSetProperties = contextType.GetProperties().Where(prop => (prop.PropertyType.IsGenericType) && prop.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>));
            foreach (var prop in dbSetProperties)
            {
                var dbSetGenericType = prop.PropertyType.GetGenericArguments()[0];
                Type listType = typeof(List<>).MakeGenericType(dbSetGenericType);
                var listForFakeTable = Activator.CreateInstance(listType);
                var parameter = Expression.Parameter(contextType);
                var body = Expression.PropertyOrField(parameter, prop.Name);
                var lambdaExpression = Expression.Lambda<Func<T, object>>(body, parameter);
                var method = typeof(EntityFrameworkMockHelper).GetMethod("MockDbSet").MakeGenericMethod(dbSetGenericType);
                mockedContext.Setup(lambdaExpression).Returns(method.Invoke(null, new[] { listForFakeTable }));
                mockedContext.Tables.Add(prop.Name, listForFakeTable);
            }
        }


    }

 

参考:

Testing with a mocking framework (EF6 onwards)

How to mock an Entity Framework DbContext and its DbSet properties

文件下载地址:http://download.csdn.net/detail/dz45693/9514948

你可能感兴趣的:(单元测试 mock EF 中DbContext 和DbSet Include)