1. 程式人生 > >利用Mocking Framework 單元測試Entity Framework

利用Mocking Framework 單元測試Entity Framework

dom class exp detached 異步 dbr cnblogs kde num

一、前言

  在實際編寫程序時,往往需要與數據庫打交道,在單元測試中直接使用數據庫又顯得太重,如果可以方便的編寫一些測試數據,這樣更易於檢測功能。如何模擬數據庫行為便是本篇的主題。微軟有教程說明Moq Entity Framework,需註意的是EF的版本必須是6以上。但在這篇教程中是直接使用DbContext,而自己的應用程序中都是用UnitOfWork模式。經過修改後也可以實現類似功能。

二、參考文獻

https://msdn.microsoft.com/en-us/data/dn314429

三、采用UnitOfWork模式管理數據庫

UnitOfWork

 public interface
IDomainUnitOfWork : IDisposable { DbContext Db { get; } //ImsDbContext dbContext { get; } Task SaveChangesClientWinAsync(); Task SaveChangesDataBaseWinAsync(); void SaveChangesClientWin(); void SaveChangesDataBaseWin(); }

Repository接口

 public
interface IDomainRepositoryAsync<T> where T : class { //Async Task<List<T>> GetAllAsync(Expression<Func<T, bool>> predicate = null); Task<T> SingleAsync(object primaryKey); Task<T> SingleOrDefaultAsync(object primaryKey); Task
<bool> IsExistsAsync(Expression<Func<T, bool>> predicate = null); //同步 IQueryable<T> GetAll(Expression<Func<T, bool>> predicate = null); T Single(object primaryKey); T SingleOrDefault(object primaryKey); bool IsExist(Expression<Func<T, bool>> predicate = null); void Add(T entity); void Update(T entity); void Delete(T entity); }

Repository實現

public class DomainRepositoryAsync<T> : IDomainRepositoryAsync<T> where T : class
    {
        private readonly IDomainUnitOfWork _unitOfWork;
        internal DbSet<T> dbSet;

        public DomainRepositoryAsync(IDomainUnitOfWork unitOfWork)
        {
            if (unitOfWork == null) throw new ArgumentNullException("unitOfWork");
            this._unitOfWork = unitOfWork;
            this.dbSet = _unitOfWork.Db.Set<T>();
        }
        public void Add(T entity)
        {
            dynamic obj = dbSet.Add(entity);
        }

        public void Delete(T entity)
        {
            if (_unitOfWork.Db.Entry(entity).State == EntityState.Detached)
            {
                dbSet.Attach(entity);
            }
            dynamic obj = dbSet.Remove(entity);
        }

        public async Task<List<T>> GetAllAsync(Expression<Func<T, bool>> predicate = null)
        {

            if (predicate != null)
            {
                return await this.dbSet.Where(predicate).ToListAsync();
            }

            return await this.dbSet.ToListAsync();
        }

        public async Task<bool> IsExistsAsync(Expression<Func<T, bool>> predicate = null)
        {
            var result = false;
            if (predicate == null)
            {
                return result;
            }
            var query = await this.dbSet.Where(predicate).FirstOrDefaultAsync();
            result = query == null ? false : true;
            return result;
        }
        /// <summary>
        /// 如果沒有找到指定鍵元素,拋出異常.
        /// </summary>
        /// <param name="primaryKey">The primary key.</param>
        /// <returns>Task&lt;T&gt;.</returns>
        /// <exception cref="System.Collections.Generic.KeyNotFoundException"></exception>
        public async Task<T> SingleAsync(object primaryKey)
        {
            T dbResult = null;
            dbResult = await dbSet.FindAsync(primaryKey);
            if (dbResult == null)
            {
                throw new KeyNotFoundException();
            }
            return dbResult;
        }

        public async Task<T> SingleOrDefaultAsync(object primaryKey)
        {
            var dbResult = await dbSet.FindAsync(primaryKey);
            return dbResult;
        }

        public void Update(T entity)
        {
            this.dbSet.Attach(entity);
            _unitOfWork.Db.Entry(entity).State = EntityState.Modified;
        }

        public IQueryable<T> GetAll(Expression<Func<T, bool>> predicate = null)
        {
            if (predicate != null)
            {
                return this.dbSet.Where(predicate);
            }

            return this.dbSet;
        }

        public T Single(object primaryKey)
        {
            T dbResult = null;
            dbResult = dbSet.Find(primaryKey);
            if (dbResult == null)
            {
                throw new KeyNotFoundException();
            }
            return dbResult;
        }

        public T SingleOrDefault(object primaryKey)
        {
            var dbResult = dbSet.Find(primaryKey);
            return dbResult;
        }

        public bool IsExist(Expression<Func<T, bool>> predicate = null)
        {
            var result = false;
            if (predicate == null)
            {
                return result;
            }
            var query = this.dbSet.Where(predicate).FirstOrDefault();
            result = query == null ? false : true;
            return result;
        }
    }

DbContext

public class DurationDbContext : DbContext
    {
        public virtual DbSet<Department> Departments { get; set; }
        public virtual DbSet<Duration> Durations { get; set; }
    }            

四、配置UnitTest

1、首先用Nuget安裝moq

2、註意在DomainRepositoryAsync中有一個DbSet<T> dbSet,需要Moq的就是該類型,並且在DbContext中的必須加"virtual“關鍵字。

3、Moq代碼

var unitOfWork = new Mock<IDomainUnitOfWork>();
 var mockDepartment = SetupMockDbSet<Department>(DepartmentList);
 var mockDuration = SetupMockDbSet<Duration>(DurationList);
 unitOfWork.Setup(m => m.Db.Set<Department>()).Returns(mockDepartment.Object);
 unitOfWork.Setup(m => m.Db.Set<Duration>()).Returns(mockDuration.Object);

在教程中說到了如何處理異步的查詢操作,教程很詳細,此處便不再重復,直接將代碼Copy到單元測試工程中即可,再將重復的代碼作為一個方法SetupMockDBSet。

 public static Mock<DbSet<T>> SetupMockDbSet<T>(List<T> dataList) where T : class
        {
            var data = dataList.AsQueryable();
            var mockSet = new Mock<DbSet<T>>();
            mockSet.As<IDbAsyncEnumerable<T>>()
               .Setup(m => m.GetAsyncEnumerator())
               .Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator()));

            mockSet.As<IQueryable<T>>()
                .Setup(m => m.Provider)
                .Returns(new TestDbAsyncQueryProvider<T>(data.Provider));

            mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
            mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
            mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
            return mockSet;
        }

註意在配置UnitOfWork時,用Setup中是對IUnitOfWork的Db進行設置。接下來的實現方式與教程相同,不再重復。

利用Mocking Framework 單元測試Entity Framework