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

  • 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. Android 之内容提供者 内容解析者 内容观察者

    contentProvider:ContentProvider在Android中的作用是对外提供数据,除了可以为所在应用提供数据外,还可以共享数据给其他应用,这是Android中解决应用之间数据共享的 ...

  2. Android忽略文件以及.gitignore规则不生效的可行解决方案

    github官方的忽略规则:https://github.com/github/gitignore/blob/master/Android.gitignore 我司项目中的忽略规则: *.iml .g ...

  3. 2013 ACM/ICPC Asia Regional Hangzhou Online hdu4739 Zhuge Liang's Mines

    Zhuge Liang's Mines Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Othe ...

  4. S2_OOP第三章

    第一章 多态 概念 多态是具有表现多种型生态的能力的特征,同一个实现接口,使用不同的实例而执行不同的操作 子类转换父类(向上转型) 用父类接受子类,向上转型 向上转型的规则: 讲一个父类的引用志向一个 ...

  5. http://codeforces.com/contest/845

    A. Chess Tourney time limit per test 1 second memory limit per test 256 megabytes input standard inp ...

  6. 一文为你详细讲解对象映射库【AutoMapper】所支持场景

    前言 在AutoMapper未出世前,对象与对象之间的映射,我们只能通过手动为每个属性一一赋值,时间长了不仅是我们而且老外也觉得映射代码很无聊啊.这个时候老外的所写的强大映射库AutoMapper横空 ...

  7. (Java后端 Java web)面试时如何展示自己非技术方面的能力(其实就是综合能力)

    这篇文章的适用范围其实不仅限于Java后端或Java Web,不过其中有些是拿这方面举例的,在其它方面,大家可以举一反三,应该也能得到些启示. 我们在面试时,会发现有些候选人技术不错,比如在Java ...

  8. unserialize() [function.unserialize]: Error at offset

    $a = 'a:1:{i:0;s:12:"1,10,93,";}'; var_dump( unserialize( $a ) ); 运行之后页面上显示Notice: unseria ...

  9. 系统出现异常: too many values to unpack (expected 2)

    先感谢[ValueError: too many values to unpack](http://leonzhan.iteye.com/blog/1720315)系统出现异常:打开太多值(预期2)这 ...

  10. Python实战之列表简单练习

    ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__ ...