• 单元测试 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

  • 相关阅读:
    关于scanf、getchar、getch、getche缓冲区分析——C语言
    堆排序(大顶堆、小顶堆)----C语言
    预处理命令使用详解----#if、#endif、#undef、#ifdef、#else、#elif
    参数传递---关于数组的退化
    控制台API函数----HANDLE、SetConsoleCursorPosition、SetConsoleTextAttribute
    二叉树的遍历(前序、中序、后序、已知前中序求后序、已知中后序求前序)
    Fiddler 插件开发,使用 WPF 作为 UI 控件
    从程序集加载类型,遇到 ReflectionTypeLoadException 的处理办法
    如何将应用程序与文件类型(文件扩展名)关联起来?
    为什么 WPF 的 Main 方法需要标记 STAThread 。
  • 原文地址:https://www.cnblogs.com/majiang/p/5475853.html
Copyright © 2020-2023  润新知