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

  • 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. MySQL集群(三)mysql-proxy搭建负载均衡与读写分离

    前言 前面学习了主从复制和主主复制,接下来给大家分享一下怎么去使用mysql-proxy这个插件去配置MySQL集群中的负载均衡以及读写分离. 注意:这里比较坑的就是mysql-proxy一直没有更新 ...

  2. Django中的信号及其用法

    Django中提供了"信号调度",用于在框架执行操作时解耦. 一些动作发生的时候,系统会根据信号定义的函数执行相应的操作 Django中内置的signal Model_signal ...

  3. oracle pl/sql 控制结构(分支,循环,控制)

    一.pl/sql的进阶--控制结构在任何计算机语言(c,java,pascal)都有各种控制语句(条件语句,循环结构,顺序控制结构...),在pl/sql中也存在这样的控制结构.在本部分学习完成后,希 ...

  4. IOS7 点击空白处隐藏键盘的几种方法

    IOS7 点击空白处隐藏键盘的几种方法   iOS开发中经常要用到输入框,默认情况下点击输入框就会弹出键盘,但是必须要实现输入框return的委托方法才能取消键盘的显示,对于用户体验来说很不友好,我们 ...

  5. JSP入门3 Servlet

    需要继承类HttpServlet 服务器在获得请求的时候会先根据jsp页面生成一个java文件,然后使用jdk的编译器将此文件编译,最后运行得到的class文件处理用户的请求返回响应.如果再有请求访问 ...

  6. 初学node.js有感三

    WebStorm下的node.js 一.回顾与继续       在前面,我们知道了node.js的基本框架和思路,在这些原生环境下我们对node.js的设计思想有了比较深刻的认识,并且具有了编写大型程 ...

  7. Paint the Grid Again ZOJ - 3780 拓扑

    Paint the Grid Again Time Limit: 2000MS   Memory Limit: 65536KB   64bit IO Format: %lld & %llu [ ...

  8. Ubuntu 16安装GPU版本tensorflow

    pre { direction: ltr; color: rgb(0, 0, 0) } pre.western { font-family: "Liberation Mono", ...

  9. ArcGIS RunTime SDK for Android之Features and graphics

    今天是我开通博客园的第一天,希望以后可以多在博客园上分享自己的学习心得,记录自己的学习历程.最近在学习ArcGIS RunTime SDK for Android,所以第一篇随笔就从这里来吧.官网的教 ...

  10. CSS3D模型

    html部分 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> < ...