在XUnit中用Moq怎样模拟EntityFramework Core下的DbSet
最近在做一個(gè)項(xiàng)目的單元測(cè)試時(shí),遇到了些問(wèn)題,解決后,覺(jué)得有必要記下來(lái),并分享給需要的人,先簡(jiǎn)單說(shuō)一下項(xiàng)目技術(shù)框架背景:
-
asp.net core 2.0(for .net core)框架
-
用Entity Framework Core作ORM
-
XUnit作單元測(cè)試
-
Moq作隔離框加
?在對(duì)業(yè)務(wù)層進(jìn)行單元測(cè)試時(shí),因?yàn)闃I(yè)務(wù)層調(diào)用到數(shù)據(jù)處理層,所以要用Moq去模擬DbContext,這個(gè)很容易做到,但如果操作DbContext下的DbSet和DbSet下的擴(kuò)展方法時(shí),就會(huì)拋出一個(gè)System.NotSupportedException異常。這是因?yàn)槲覀儧](méi)辦法Mock DbSet,并助DbSet是個(gè)抽象類,還沒(méi)有辦法實(shí)例化。
其實(shí),這個(gè)時(shí)候我們希望的是,如果用一個(gè)通用的集合,比如List<T>集合,或T[]數(shù)組來(lái)Mock DbSet<T>,就非常舒服了,因?yàn)榧匣驍?shù)組的元素我們非常容易模擬或控制,不像DbSet。
深挖DbSet下常用的這些擴(kuò)展方法:Where,Select,SingleOrDefault,FirstOrDefault,OrderBy等,都是對(duì)IQueryable的擴(kuò)展,也就是說(shuō)把對(duì)DbSet的這些擴(kuò)展方法的調(diào)用轉(zhuǎn)成Mock List<T>或T[]的擴(kuò)展方法調(diào)用就OK了,
所以實(shí)現(xiàn)下的類型:
項(xiàng)目需要引入:Microsoft.EntityFrameworkCore 和Moq,Nuget可以引入。
UnitTestAsyncEnumerable.cs
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace MoqEFCoreExtension
{
? ? /// <summary>
? ? /// 自定義實(shí)現(xiàn)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>
? ? /// 定義關(guān)現(xiàn)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>
? ? /// 實(shí)現(xiàn)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));
? ? ? ? }
? ? }
}
擴(kuò)展方法類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">實(shí)體類型</typeparam>
? ? ? ? /// <param name="mockSet">Mock<DbSet>對(duì)象</param>
? ? ? ? /// <param name="list">實(shí)體列表</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>
? ? ? ? /// 加載數(shù)據(jù)到DbSet
? ? ? ? /// </summary>
? ? ? ? /// <typeparam name="T">實(shí)體類型</typeparam>
? ? ? ? /// <param name="mockSet">Mock<DbSet>對(duì)象</param>
? ? ? ? /// <param name="array">實(shí)體數(shù)組</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);替換擴(kuò)展方法,以至于在answerRepository.ModifyAnswer(answer)中調(diào)用SingleOrDefault時(shí),操作的是具有兩個(gè)answers的list,而非DbSet。
源碼和Sample:https://github.com/axzxs2001/MoqEFCoreExtension
同時(shí),我把這個(gè)功能封閉成了一個(gè)Nuget包,參見:https://www.nuget.org/packages/MoqEFCoreExtension/
最后上一個(gè)圖壓壓驚:
原文地址:http://www.cnblogs.com/axzxs2001/p/7777311.html
NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的在XUnit中用Moq怎样模拟EntityFramework Core下的DbSet的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Julia女神告诉我任何一家企业本质上都
- 下一篇: ASP.NET Core 认证与授权[4