如今EF愈來愈流行了,不少時候業務成都是直接訪問DbContext 和DbSet來操做數據的。 那麼咱們測試的時候如何來mock這2個對象了?如今時間很晚了, 就直接貼code吧ide
首先看看的咱們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 實例方法。ui
單元測試code:this
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 這裏我單獨寫了一個 serviceurl
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在同一個 程序集裏面, 實際上咱們應該分開的。spa
EntityFrameworkMockHelper 的實現以下:.net
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); } } }
參考:code
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