在XUnit中用Moq怎樣模擬EntityFramework Core下的DbSet

来源:http://www.cnblogs.com/axzxs2001/archive/2017/11/03/7777311.html
-Advertisement-
Play Games

在對業務層進行單元測試時,因為業務層調用到數據處理層,所以要用Moq去模擬DbContext,這個很容易做到,但如果操作DbContext下的DbSet和DbSet下的擴展方法時,就會拋出一個System.NotSupportedException異常。這是因為我們沒辦法Mock DbSet,並助D... ...


最近在做一個項目的單元測試時,遇到了些問題,解決後,覺得有必要記下來,並分享給需要的人,先簡單說一下項目技術框架背景:

  • 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

 1 using System.Collections.Generic;
 2 using System.Linq;
 3 using System.Linq.Expressions;
 4 
 5 namespace MoqEFCoreExtension
 6 {
 7     /// <summary>
 8     /// 自定義實現EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>類型
 9     /// </summary>
10     /// <typeparam name="T"></typeparam>
11     class UnitTestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
12     {
13         public UnitTestAsyncEnumerable(IEnumerable<T> enumerable)
14             : base(enumerable)
15         { }
16 
17         public UnitTestAsyncEnumerable(Expression expression)
18             : base(expression)
19         { }
20 
21         public IAsyncEnumerator<T> GetEnumerator()
22         {
23             return new UnitTestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
24         }
25 
26         IQueryProvider IQueryable.Provider
27         {
28             get { return new UnitTestAsyncQueryProvider<T>(this); }
29         }
30     }
31 }

UnitTestAsyncEnumerator.cs

 1 using System.Collections.Generic;
 2 using System.Threading;
 3 using System.Threading.Tasks;
 4 
 5 namespace MoqEFCoreExtension
 6 {
 7     /// <summary>
 8     /// 定義關現IAsyncEnumerator<T>類型
 9     /// </summary>
10     /// <typeparam name="T"></typeparam>
11     class UnitTestAsyncEnumerator<T> : IAsyncEnumerator<T>
12     {
13         private readonly IEnumerator<T> _inner;
14 
15         public UnitTestAsyncEnumerator(IEnumerator<T> inner)
16         {
17             _inner = inner;
18         }
19 
20         public void Dispose()
21         {
22             _inner.Dispose();
23         }
24 
25         public T Current
26         {
27             get
28             {
29                 return _inner.Current;
30             }
31         }
32 
33         public Task<bool> MoveNext(CancellationToken cancellationToken)
34         {
35             return Task.FromResult(_inner.MoveNext());
36         }
37     }
38 }

UnitTestAsyncQueryProvider.cs

 1 using Microsoft.EntityFrameworkCore.Query.Internal;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Linq.Expressions;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace MoqEFCoreExtension
 9 {
10     /// <summary>
11     /// 實現IQueryProvider介面
12     /// </summary>
13     /// <typeparam name="TEntity"></typeparam>
14     class UnitTestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
15     {
16         private readonly IQueryProvider _inner;
17 
18         internal UnitTestAsyncQueryProvider(IQueryProvider inner)
19         {
20             _inner = inner;
21         }
22 
23         public IQueryable CreateQuery(Expression expression)
24         {
25             return new UnitTestAsyncEnumerable<TEntity>(expression);
26         }
27 
28         public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
29         {
30             return new UnitTestAsyncEnumerable<TElement>(expression);
31         }
32 
33         public object Execute(Expression expression)
34         {
35             return _inner.Execute(expression);
36         }
37 
38         public TResult Execute<TResult>(Expression expression)
39         {
40             return _inner.Execute<TResult>(expression);
41         }
42 
43         public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
44         {
45             return new UnitTestAsyncEnumerable<TResult>(expression);
46         }
47 
48         public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
49         {
50             return Task.FromResult(Execute<TResult>(expression));
51         }
52     }
53 }

擴展方法類EFSetupData.cs

 1 using Microsoft.EntityFrameworkCore;
 2 using Moq;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 
 6 
 7 namespace MoqEFCoreExtension
 8 {
 9     /// <summary>
10     /// Mock Entity Framework Core中DbContext,載入List<T>或T[]到DbSet<T>
11     /// </summary>
12     public static class EFSetupData
13     {
14         /// <summary>
15         /// 載入List<T>到DbSet
16         /// </summary>
17         /// <typeparam name="T">實體類型</typeparam>
18         /// <param name="mockSet">Mock<DbSet>對象</param>
19         /// <param name="list">實體列表</param>
20         /// <returns></returns>
21         public static Mock<DbSet<T>> SetupList<T>(this Mock<DbSet<T>> mockSet, List<T> list) where T : class
22         {
23             return mockSet.SetupArray(list.ToArray());
24         }
25         /// <summary>
26         /// 載入數據到DbSet
27         /// </summary>
28         /// <typeparam name="T">實體類型</typeparam>
29         /// <param name="mockSet">Mock<DbSet>對象</param>
30         /// <param name="array">實體數組</param>
31         /// <returns></returns>
32         public static Mock<DbSet<T>> SetupArray<T>(this Mock<DbSet<T>> mockSet, params T[] array) where T : class
33         {
34             var queryable = array.AsQueryable();
35             mockSet.As<IAsyncEnumerable<T>>().Setup(m => m.GetEnumerator()).Returns(new UnitTestAsyncEnumerator<T>(queryable.GetEnumerator()));
36             mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(new UnitTestAsyncQueryProvider<T>(queryable.Provider));
37             mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
38             mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
39             mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
40             return mockSet;
41         }
42     }
43 }

  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/

最後上一個圖壓壓驚:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Spring簡介 網站: 複雜的Java EE項目用Spring才會得到優化,如果太簡單的項目用框架反而會變的麻煩。 ...
  • 背景 最近興趣使然寫了幾個Python庫,也發佈到了Pypi上,雖然沒什麼人下載,但自己在其他機器上用著也會很方便。這裡我向大家介紹一下如何在Pypi上發表自己的Python庫。 準備 註冊賬號 很顯然地要在Pypi上註冊一個賬號。 安裝必要的庫 setuptools 原則上安裝了pip的環境都有s ...
  • 初學者,寫的不好請指出。 #第一步以insertTime為條件查詢時間段內的數據 #第二部步可以選擇是否再以通話Id為條件篩選第一步所查詢出來的數據 #因為使用的是配置文件,所以首先在代碼當前目錄下創建一個配置文件,db.conf 代碼: ...
  • 面向對象編程的基本理念與核心設計思想 解釋下多態性(polymorphism),封裝性(encapsulation),內聚(cohesion)以及耦合(coupling)。 繼承(Inheritance)與聚合(Aggregation)的區別在哪裡。 你是如何理解乾凈的代碼(Clean Code)與 ...
  • 前面文章介紹瞭如何使用Identity在ASP.NET MVC中實現用戶的註冊、登錄以及身份驗證。這些功能都是與用戶信息安全相關的功能,數據安全的重要性永遠放在第一位。那麼對於註冊和登錄功能來說要把密碼及用戶其它信息通過表單的形式安全的提交到伺服器上,那麼最適合的方法就是使用HTTPS(如果有條件或 ...
  • 昨天分享的博文《angularjs呼叫Web API》http://www.cnblogs.com/insus/p/7772022.html,只是從Entity獲取數據,沒有進行參數POST。 今天分享一個例子,是傳遞參數至Web API來獲取數據的。而且數據是存儲在SQL中。數表結構是昨晚幫助網友 ...
  • 目前想做一個類似這樣的功能,通過win32 api 獲取到一個視窗程式中的Combox句柄,然後根據這個句柄設置Comboxd的選項同時觸發Comobox的selectIndexChange事件。目前僅實現了Combox中設置值,對於如何觸發selectIndexChange沒有頭緒,希望園中的大神 ...
  • //TransmitFile實現下載 protected void Button1_Click(object sender, EventArgs e) { /* 微軟為Response對象提供了一個新的方法TransmitFile來解決使用Response.BinaryWrite 下載超過400mb ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...