在XUnit中用Moq怎样模拟EntityFramework Core下的DbSet
最近在做一個項目的單元測試時,遇到了些問題,解決后,覺得有必要記下來,并分享給需要的人,先簡單說一下項目技術框架背景:
-
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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Julia女神告诉我任何一家企业本质上都
- 下一篇: 使用Identity Server 4建