ASP.NET Core 練習 - EF Core 單元測試
2 |
想用單元測試專案單獨測試 ASP.NET Core 裡的 EF Core DbContext,一時間傻住不知如何下手,就知道又有基本功要練了。ASP.NET Core 重度依賴 DI,網站專案如要使用 EF Core DbContext 物件需在 Controller 或 Service 建構式新增 DbContext 型別的參數,DI 會在建立 Controller 或 Service 時傳入 DbContext Instance,但,在單元測試專案該怎麼做?
先來個題外話,.NET Core 的測試專案有三種選擇:MSTest、NUnit 及 xUnit:
(三種專案的比較可參考 MSTest, NUnit 3, xUnit.net 2.0 比較 by Yowko ,我只有 MSTest 經驗,選擇時反而沒有懸念,噗)
爬文找到兩種解法。在此借用 ASP.NET Core 新增修改刪除(CRUD)介面傻瓜範例 專案裡的 JournalDbContext 示範:
借用 Startup 註冊邏輯
ASP.NET Core 網站執行時,Program.cs 會執行 IWebHostBuilder.UseStartup<Startup>() 觸發 Startup.cs 的 ConfigureServices(IServiceCollection services),透過以下邏輯指定 EF Core 來源為 SQL Server 並傳入連線字串:
services.AddDbContextPool<JournalDbContext>(options =>
{
//TODO: 實際應用時,連線字串應移入設定檔並加密儲存
options.UseSqlServer(Configuration.GetConnectionString("LocalDB"));
});
依此原理,我們只需在單元測試專案裡也模擬 IWebHostBuilder.UseStartup<Startup>() 便能直接使用 Startup.cs 的資料庫設定。範例如下:
using CRUDExample;
using CRUDExample.Models;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
namespace TestProject
{
[TestClass]
public class UnitTest1
{
static IWebHost _webHost = null;
static T GetService<T>()
{
var scope = _webHost.Services.CreateScope();
return scope.ServiceProvider.GetRequiredService<T>();
}
[ClassInitialize]
public static void Init(TestContext testContext)
{
_webHost = WebHost.CreateDefaultBuilder()
.UseStartup<Startup>()
.Build();
}
[TestMethod]
public void TestMethod1()
{
var ctx = GetService<JournalDbContext>();
var rec = ctx.Records.FirstOrDefault();
Assert.IsNotNull(rec);
}
}
}
測試成功!
自行建立 DbContext
有另一種情境,測試時期 EF Core 將使用不同的資料庫來源(例如平日測試連 SQL,單元測試則另開 LocalDB 資料庫),以免自動測試寫入亂七八糟資料影響日常開發,或是測試時直接改用可拋式的 InMemory 資料來源,測完即丟。
以下展示改用 InMemory 資料庫執行自動測試,EF Core 再次展現它跨資料庫的威力,由 UseSqlServer() 改為 UseInMemoryDatabase() 就變成用記憶體模擬資料庫,來去不留痕跡。
using CRUDExample.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestProject
{
[TestClass]
public class UnitTest2
{
JournalDbContext GetDbContext()
{
var options = new DbContextOptionsBuilder<JournalDbContext>()
.UseInMemoryDatabase(databaseName: "TestDb")
.Options;
return new JournalDbContext(options);
}
[TestMethod]
public void TestMethod2()
{
var ctx = GetDbContext();
var data = new DailyRecord()
{
Date = DateTime.Now,
EventSummary = "No Events",
Remark = "ABC",
Status = StatusFlags.Warn,
User = "Jeffrey"
};
ctx.Records.Add(data);
ctx.SaveChanges();
var rec = ctx.Records.FirstOrDefault();
Assert.IsNotNull(rec);
}
}
}
成功!
以上就是在單元測試使用 ASP.NET Core 內含 EF Core 程式的簡單示範。
Tips of how to access EF Core DbContext in ASP.NET Core project from unit test project.
Comments
# by 凱大
滿多人習慣慣用 real db 來作為測試 這其實並無法真的實現一個完整的測試 InMemory 完整了模組化所需要的東西 不過InMemory並不是模擬 Db 所以概念上對某些人而言可能反而並不那麼實用 (雖然我覺得這屬於他對於測試的不理解問題 XD) 相關MSDN: https://docs.microsoft.com/zh-tw/ef/core/miscellaneous/testing/in-memory
# by Jeffrey
to 凱大,雖然是 UnitTest 專案,有些人是用它跑涵蓋資料庫的自動化整合測試,也許不夠道地沒離TDD的精神也還很遠,但我認為有做到這一步已勝過完全不測試、手工測試或是上線玩全民公測,仍有成長空間但值得鼓勵。