zoukankan      html  css  js  c++  java
  • 利用Mocking Framework 单元测试Entity Framework

    一、前言

      在实际编写程序时,往往需要与数据库打交道,在单元测试中直接使用数据库又显得太重,如果可以方便的编写一些测试数据,这样更易于检测功能。如何模拟数据库行为便是本篇的主题。微软有教程说明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进行设置。接下来的实现方式与教程相同,不再重复。

  • 相关阅读:
    第一行DOCTYPE 的作用
    es6 proxy、handler.get()
    vue router-link 默认a标签去除下划线
    打开记事本
    JS数组遍历的方法
    vue项目中使用proxy解决跨域
    封装axios
    postMessage vue iframe传值
    input限制只能输入数字,且保留小数后两位
    axios封装
  • 原文地址:https://www.cnblogs.com/hahaxi/p/6977733.html
Copyright © 2011-2022 走看看