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

  • 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/

最后上一个图压压惊:

原文地址:http://www.cnblogs.com/axzxs2001/p/7777311.html


NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

在XUnit中用Moq怎样模拟EntityFramework Core下的DbSet相关推荐

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

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

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

    .NET Core 1.1单元测试问题 我们循序渐进,首先从单元测试开始说起,可能其中就有你在.NET Core上进行单元测试会遇到的问题,别着急,不妨一看.我们需要创建.NET Core类库,,如下 ...

  3. EntityFramework Core解决并发详解

    前言 对过年已经无感,不过还是有很多闲暇时间来学学东西和多陪陪爸妈,这一点是极好的,好了,本节我们来讲讲EntityFramework Core中的并发问题. 话题(EntityFramework C ...

  4. EntityFramework 6.x和EntityFramework Core必须需要MultipleActiveResultSets?

    前言 本节我们来探讨到底需不需要在连接字符串上加上MultipleActiveResultSets = true ?,若您有更深层次的理解欢迎留下您的脚印. EntityFramework 6.x和E ...

  5. ASP.NET Core Identity自定义数据库结构和完全使用Dapper而非EntityFramework Core

    前言 原本本节内容是不存在的,出于有几个人问到了我:我想使用ASP.NET Core Identity,但是我又不想使用默认生成的数据库表,想自定义一套,我想要使用ASP.NE Core Identi ...

  6. EntityFramework Core查询数据基本本质

    [导读]在EntityFramework Core中.当查询出数据后,是如何将数据映射给实体的呢?本节我们预先做个基本探讨,后续给出其底层原理本质 前不久,我们在探索性能时,给出利用反射达到性能瓶颈时 ...

  7. EntityFramework Core如何映射动态模型?

    [导读]本文我们来探讨下映射动态模型的几种方式,相信一部分童鞋项目有这样的需求,比如每天/每小时等生成一张表,此种动态模型映射非常常见,经我摸索,这里给出每一步详细思路,希望能帮助到没有任何头绪的童鞋 ...

  8. EntityFramework Core 健康检查

    [导读].NET Core提供对应方法可进行健康检查,那么在EF Core中是否也提供了相应的方式呢? EF Core 2.2+(包含2.2)版本提供了针对上下文的健康检查,接下来我们直接利用.NET ...

  9. EntityFramework Core上下文实例池原理

    [导读]无论是在我个人博客还是著作中,对于上下文实例池都只是通过大量文字描述来讲解其基本原理,而且也是浅尝辄止,导致我们对其认识仍是一知半解,本文我们摆源码,从源头开始分析 希望通过本文从源码的分析, ...

最新文章

  1. Java连MySQL性能调优(batch insert和连续left join筛选)
  2. Android的内存优化的几种方案
  3. 云南省2021高考成绩查询时间,2021云南高考成绩什么时候几点可以查
  4. Qt for Android 自定义启动页(解决启动页拉伸的问题)
  5. Java内置锁——synchronized
  6. linux看请求报文发送的ip,Linux C 实现最简单的ICMP_ECHO请求报文发送
  7. maven常用插件: 打包源码 / 跳过测试 / 单独打包依赖项
  8. win7自定义html为桌面,Win7系统如何自定义个性桌面?
  9. hprose-php教程,hprose php用户手册
  10. android日历订阅,Android日历.
  11. 网易考拉布局和css样式
  12. element-ui文件上传修改上传文件的格式
  13. Illegal unquoted character ((CTRL-CHAR, code 13)): has to be escaped using backs
  14. 「Arm Arch」 ISA 概述
  15. 康特EPON OLT开局配置
  16. 逻辑思维强的人适合学计算机不,逻辑思维强的人适合什么工作?
  17. 火狐浏览器中设置打开新地址时,不会覆盖原页面的方法
  18. 半钧先生:分享一波超赞的冬至文案,句句暖到心!
  19. 用CSS实现三角形及其原理
  20. Swift 头像上传(2)http://blog.csdn.net/wei_chong_chong/article/details/52611110

热门文章

  1. MATLAB中求矩阵非零元的坐标
  2. maven3安装和使用笔记
  3. android 蓝牙各种UUID(转载)
  4. Linux SSH Publickey登录
  5. 关于商品分类 商品表和属性表的设计
  6. 限流中间件IpRateLimitMiddleware的使用
  7. 45岁,一个平凡大叔的异地打工生活
  8. Juster的MVP奋斗之路
  9. 刘敏:优麒麟开源操作系统运营实践 | DEV. Together 2021 中国开发者生态峰会
  10. 工业互联网的两种极端想法和两点反思