最近在做一个项目的单元测试时,遇到了些问题,解决后,觉得有必要记下来,并分享给需要的人,先简单说一下项目技术框架背景:

  • asp.net core 2.0(for .net core)框架
  • 用Entity Framework Core作ORM
  • XUnit作单元测试
  • Moq作隔离框加

在对业务层进行单元测试时,因为业务层调用到数据处理层,所以要用Moq去模拟DbContext,这个很容易做到,但如果操作DbContext下的DbSet和DbSet下的扩展方法时,就会抛出一个System.NotSupportedException异常。这是因为我们没办法Mock DbSet,并助DbSet是个抽象类,还没有办法实例化。

其实,这个时候我们希望的是,如果用一个通用的集合,比如List<T>集合,或T[]数组来Mock DbSet<T>,就非常舒服了,因为集合或数组的元素我们非常容易模拟或控制,不像DbSet。

深挖DbSet下常用的这些扩展方法:Where,Select,SingleOrDefault,FirstOrDefault,OrderBy等,都是对IQueryable的扩展,也就是说把对DbSet的这些扩展方法的调用转成Mock List<T>或T[]的扩展方法调用就OK了,

所以实现下的类型:

项目需要引入:Microsoft.EntityFrameworkCore 和Moq,Nuget可以引入。

UnitTestAsyncEnumerable.cs

 using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions; namespace MoqEFCoreExtension
{
/// <summary>
/// 自定义实现EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>类型
/// </summary>
/// <typeparam name="T"></typeparam>
class UnitTestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
public UnitTestAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ } public UnitTestAsyncEnumerable(Expression expression)
: base(expression)
{ } public IAsyncEnumerator<T> GetEnumerator()
{
return new UnitTestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
} IQueryProvider IQueryable.Provider
{
get { return new UnitTestAsyncQueryProvider<T>(this); }
}
}
}

UnitTestAsyncEnumerator.cs

 using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; namespace MoqEFCoreExtension
{
/// <summary>
/// 定义关现IAsyncEnumerator<T>类型
/// </summary>
/// <typeparam name="T"></typeparam>
class UnitTestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner; public UnitTestAsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
} public void Dispose()
{
_inner.Dispose();
} public T Current
{
get
{
return _inner.Current;
}
} public Task<bool> MoveNext(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
}
}

UnitTestAsyncQueryProvider.cs

 using Microsoft.EntityFrameworkCore.Query.Internal;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks; namespace MoqEFCoreExtension
{
/// <summary>
/// 实现IQueryProvider接口
/// </summary>
/// <typeparam name="TEntity"></typeparam>
class UnitTestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
private readonly IQueryProvider _inner; internal UnitTestAsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
} public IQueryable CreateQuery(Expression expression)
{
return new UnitTestAsyncEnumerable<TEntity>(expression);
} public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new UnitTestAsyncEnumerable<TElement>(expression);
} public object Execute(Expression expression)
{
return _inner.Execute(expression);
} public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
} public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
{
return new UnitTestAsyncEnumerable<TResult>(expression);
} public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
}

扩展方法类EFSetupData.cs

 using Microsoft.EntityFrameworkCore;
using Moq;
using System.Collections.Generic;
using System.Linq; namespace MoqEFCoreExtension
{
/// <summary>
/// Mock Entity Framework Core中DbContext,加载List<T>或T[]到DbSet<T>
/// </summary>
public static class EFSetupData
{
/// <summary>
/// 加载List<T>到DbSet
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
/// <param name="mockSet">Mock<DbSet>对象</param>
/// <param name="list">实体列表</param>
/// <returns></returns>
public static Mock<DbSet<T>> SetupList<T>(this Mock<DbSet<T>> mockSet, List<T> list) where T : class
{
return mockSet.SetupArray(list.ToArray());
}
/// <summary>
/// 加载数据到DbSet
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
/// <param name="mockSet">Mock<DbSet>对象</param>
/// <param name="array">实体数组</param>
/// <returns></returns>
public static Mock<DbSet<T>> SetupArray<T>(this Mock<DbSet<T>> mockSet, params T[] array) where T : class
{
var queryable = array.AsQueryable();
mockSet.As<IAsyncEnumerable<T>>().Setup(m => m.GetEnumerator()).Returns(new UnitTestAsyncEnumerator<T>(queryable.GetEnumerator()));
mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(new UnitTestAsyncQueryProvider<T>(queryable.Provider));
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
return mockSet;
}
}
}

var answerSet = new Mock<DbSet<Answers>>().SetupList(list);替换扩展方法,以至于在answerRepository.ModifyAnswer(answer)中调用SingleOrDefault时,操作的是具有两个answers的list,而非DbSet。

源码和Sample:https://github.com/axzxs2001/MoqEFCoreExtension

同时,我把这个功能封闭成了一个Nuget包,参见:https://www.nuget.org/packages/MoqEFCoreExtension/

最后上一个图压压惊:

在XUnit中用Moq怎样模拟EntityFramework Core下的DbSet的更多相关文章

  1. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  2. EntityFramework Core 1.1有哪些新特性呢?我们需要知道

    前言 在项目中用到EntityFramework Core都是现学现用,及时发现问题及时测试,私下利用休闲时间也会去学习其他未曾遇到过或者用过的特性,本节我们来讲讲在EntityFramework C ...

  3. EntityFramework Core并发导致显示插入主键问题

    前言 之前讨论过EntityFramework Core中并发问题,按照官网所给并发冲突解决方案以为没有什么问题,但是在做单元测试时发现too young,too siimple,下面我们一起来看看. ...

  4. EntityFramework Core 学习系列(一)Creating Model

    EntityFramework Core 学习系列(一)Creating Model Getting Started 使用Command Line 来添加 Package  dotnet add pa ...

  5. EntityFramework Core并发导致显式插入主键问题

    前言 之前讨论过EntityFramework Core中并发问题,按照官网所给并发冲突解决方案以为没有什么问题,但是在做单元测试时发现too young,too simple,下面我们一起来看看. ...

  6. 你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制

    你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...

  7. EntityFramework Core Raw SQL

    前言 本节我们来讲讲EF Core中的原始查询,目前在项目中对于简单的查询直接通过EF就可以解决,但是涉及到多表查询时为了一步到位就采用了原始查询的方式进行.下面我们一起来看看. EntityFram ...

  8. 神马玩意,EntityFramework Core 1.1又更新了?走,赶紧去围观

    前言 哦,不搞SQL了么,当然会继续,周末会继续更新,估计写完还得几十篇,但是我会坚持把SQL更新完毕,绝不会烂尾,后续很长一段时间没更新的话,不要想我,那说明我是学习新的技能去了,那就是学习英语,本 ...

  9. Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

随机推荐

  1. RobotFramework自动化测试框架-移动手机自动化测试Clear Text关键字的使用

    Clear Text关键字用来清除输入框的数据,该关键字接收一个参数[ locator ],这里的locator指的就是界面元素的定位方式. 示例1:Clear Text清除输入框数据时,采用reso ...

  2. GUI TextField

    GUI.TextField   public static function TextField(position: Rect, text: string): string; public stati ...

  3. React——高阶组件

    1.在React中higher-order component (HOC)是一种重用组件逻辑的高级技术.HOC不是React API中的一部分.HOC是一个函数,该函数接收一个组件并且返回一个新组件. ...

  4. JS之脚本延迟

    自从开了博客,我就一下班回来匆匆吃完饭门一关等一开电脑一打开匆匆的研究东西,以至于朋友们都怀疑我是不是都得了自闭症 其实因为我有恐惧心理怕自己的技术哪天跟不上社会了,说到技术我觉得技术不求越新越好,但 ...

  5. Theano学习-scan循环

    \(1.Scan\) 通用的一般形式,可用于循环 减少和映射(对维数循环)是特殊的 \(scan\) 对输入序列进行 \(scan\) 操作,每一步都能得到一个输出 \(scan\) 能看到定义函数的 ...

  6. Ubuntu16.04 install eclipse-jee-oxygen-R-linux-gtk-x86_64

    下面如何在Ubuntu16.04 下面怎么下载Java EE并创建在桌面快捷上下载Java EE:eclipse下载Java EE官网:http://www.eclipse.org/downloads ...

  7. FPGA在电平接口领域的应用

    电子技术的发展,产生了各种各样的电平接口. TTL电平: TTL电平信号之所以被广泛使用,原因是因为:通常我们采用二进制来表示数据.而且规定,+5V等价于逻辑"1",0V等价于逻辑 ...

  8. 常用硬件设备GUID

    Class GUID Device Description CDROM 4D36E965-E325-11CE-BFC1-08002BE10318 CD/DVD/Blu-ray drives DiskD ...

  9. ASP.NET Core 处理 404 Not Found

    问题 在没有修改任何配置的情况下,这是用户使用 Chrome 访问不存在的URL时会看到的内容: 幸运的是,处理错误状态代码非常简单,我们将在下面介绍三种技术. 解决方案 在以前的ASP.NET MV ...

  10. 全球多个 TOP 网站藏挖矿代码,5 亿 PC 沦为矿工

    据ZDNet报道,现在很多网站都开始在网页脚本中藏匿挖矿代码,在用户访问时偷算力用于挖矿.来自Adguard的报告称也证实,也有5亿台电脑中招. 最新最热的IT技术付费社区 IT帮 itbang.me ...