ASP.NET Core 支援 Razor Pages 應用程式的單元測試。 資料存取層 (DAL) 和頁面模型的測試有助於確保:
- 在應用程式建構期間,Razor 頁面應用程式的元件既能獨立運作,也可作為一個單元。
- 類別和方法的責任範圍有限。
- 另有其他文件說明應用程式應如何運行。
- 回歸是由程式碼更新帶來的錯誤,會在自動化建置和部署過程中發現。
本主題假設您已基本瞭解 Razor Pages 應用程式和單元測試。 如果您不熟悉 Razor Pages 應用程式或測試概念,請參閱下列主題:
檢視或下載範例程式碼 \(英文\) (如何下載)
範例專案是由兩個應用程式所組成:
| App | 專案資料夾 | Description |
|---|---|---|
| 訊息應用程式 | src/RazorPagesTestSample | 允許使用者新增訊息、刪除一則訊息、刪除所有訊息,以及分析訊息 (查明每則訊息的平均字數)。 |
| 測試應用程式 | tests/RazorPagesTestSample.Tests | 用來單元測試訊息應用程式的 DAL 和索引頁面模型。 |
您可以使用 IDE 的內建測試功能,例如 Visual Studio,來執行測試。 如果使用 Visual Studio Code 或命令列,請在 tests/RazorPagesTestSample.Tests 資料夾中的命令提示字元執行下列命令:
dotnet test
訊息應用程式組織
訊息應用程式是具有下列特性的 Razor Pages 訊息系統:
- 應用程式的 [索引] 頁面 (
Pages/Index.cshtml和Pages/Index.cshtml.cs) 提供 UI 和頁面模型方法來控制訊息的新增、刪除和分析 (查明每則訊息的平均字數)。 - 訊息由
Message類別 (Data/Message.cs) 來描述,其中包含兩個屬性:Id(索引鍵) 和Text(訊息)。Text屬性為必要屬性,且限制為 200 個字元。 - 訊息會使用 Entity Framework 的記憶體內部資料庫來儲存†。
- 應用程式在其資料庫內容類別
AppDbContext(Data/AppDbContext.cs) 中包含 DAL。 DAL 方法會標示為virtual,可模擬測試中使用的方法。 - 如果應用程式啟動時資料庫是空的,訊息存放區會以三則訊息初始化。 這些植入的訊息也會用於測試中。
†EF 主題 使用 InMemory 測試,說明如何使用記憶體內部資料庫搭配 MSTest 進行測試。 本主題使用 xUnit 測試架構。 不同測試架構的測試概念和測試實作彼此相似但並不相同。
雖然範例應用程式不使用存放庫模式,也不是工作單位 (UoW) 模式的有效範例,但 Razor Pages 支援此類開發模式。 如需詳細資訊,請參閱設計基礎結構持續性層和測試 ASP.NET Core 中的控制器邏輯 (範例會實作存放庫模式)。
測試應用程式組織
測試應用程式是 tests/RazorPagesTestSample.Tests 資料夾內的主控台應用程式。
| 測試應用程式資料夾 | Description |
|---|---|
| UnitTests |
|
| Utilities | 包含用來為每個 DAL 單元測試建立新資料庫內容選項的 TestDbContextOptions 方法,讓資料庫重設為每個測試的基準條件。 |
資料存取層 (DAL) 的單元測試
訊息應用程式具有 DAL,其中包含 AppDbContext 類別 (src/RazorPagesTestSample/Data/AppDbContext.cs) 中所含的四種方法。 每個方法在測試應用程式中都有一或兩個單元測試。
| DAL 方法 | Function |
|---|---|
GetMessagesAsync |
從依 List<Message> 屬性排序的資料庫取得 Text。 |
AddMessageAsync |
將 Message 加入至資料庫。 |
DeleteAllMessagesAsync |
從資料庫刪除所有 Message 項目。 |
DeleteMessageAsync |
依 Message 從資料庫刪除單一 Id。 |
為每個測試建立新的 DbContextOptions 時,DAL 的單元測試需要 AppDbContext。 為每個測試建立 DbContextOptions 的其中一種方法是使用 DbContextOptionsBuilder:
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
這種方法的問題在於,每個測試都會以先前測試留下的任何狀態接收資料庫。 嘗試撰寫不會互相干擾的不可部分完成單元測試時,這可能會造成問題。 若要強制 AppDbContext 針對每個測試使用新的資料庫內容,請提供以新服務提供者為基礎的 DbContextOptions 實例。 測試應用程式會示範如何使用其 Utilities 類別方法 TestDbContextOptions (tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs) 執行這項操作:
public static DbContextOptions<AppDbContext> TestDbContextOptions()
{
// Create a new service provider to create a new in-memory database.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Create a new options instance using an in-memory database and
// IServiceProvider that the context should resolve all of its
// services from.
var builder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb")
.UseInternalServiceProvider(serviceProvider);
return builder.Options;
}
在 DAL 單元測試中使用 DbContextOptions,可讓每個測試使用全新的資料庫實例以不可部分完成的方式執行:
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
DataAccessLayerTest 類別 (UnitTests/DataAccessLayerTest.cs) 中的每個測試方法都依循類似的 Arrange-Act-Assert 模式:
- Arrange (安排):資料庫已針對測試進行設定,並/或已定義預期的結果。
- Act (作動):執行測試。
- Assert (斷定):做出判斷以確定測試結果是否成功。
例如,DeleteMessageAsync 方法負責移除其 Id (src/RazorPagesTestSample/Data/AppDbContext.cs) 所識別的單一訊息:
public async virtual Task DeleteMessageAsync(int id)
{
var message = await Messages.FindAsync(id);
if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}
這種方法有兩個測試。 其中一項測試會檢查當訊息出現在資料庫中時,此方法是否會刪除該訊息。 另一方法則測試如果要刪除的訊息 Id 不存在,資料庫是否不會變更。
DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound 方法如下所示:
[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
首先,該方法會執行 Arrange 步驟,其間會進行 Act 步驟的安排準備。 已取得植入訊息並保留在 seedMessages 中。 植入訊息已儲存到資料庫中。
Id 為 1 的訊息已完成刪除設定。 執行 DeleteMessageAsync 方法時,預期的訊息應包含除了 Id 為 1 之訊息以外的所有訊息。
expectedMessages 變數代表這個預期的結果。
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
方法開始作動:執行 DeleteMessageAsync 方法,並傳人 recId 的 1:
// Act
await db.DeleteMessageAsync(recId);
最後,該方法從內容取得 Messages,並將其與 expectedMessages 進行比較以斷定兩者相等:
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
為了比較兩個 List<Message> 是否相同:
- 訊息會依
Id排序。 - 訊息組會就
Text屬性進行比較。
類似的測試方法是,DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound 檢查嘗試刪除不存在之訊息的結果。 在此情況下,資料庫中的預期訊息應該等於執行 DeleteMessageAsync 方法之後的實際訊息。 而資料庫的內容應該不會有任何變更:
[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var expectedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(expectedMessages);
await db.SaveChangesAsync();
var recId = 4;
// Act
try
{
await db.DeleteMessageAsync(recId);
}
catch
{
// recId doesn't exist
}
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
頁面模型方法的單元測試
另一組單元測試負責頁面模型方法的測試。 在訊息應用程式中,會在 IndexModel 的 src/RazorPagesTestSample/Pages/Index.cshtml.cs 類別中找到 [索引] 頁面模型。
| 頁面模型方法 | Function |
|---|---|
OnGetAsync |
使用 GetMessagesAsync 方法,從 DAL 取得 UI 的訊息。 |
OnPostAddMessageAsync |
如果 ModelState 有效,則呼叫 AddMessageAsync 將訊息新增至資料庫。 |
OnPostDeleteAllMessagesAsync |
呼叫 DeleteAllMessagesAsync,刪除資料庫中的所有訊息。 |
OnPostDeleteMessageAsync |
執行 DeleteMessageAsync 以刪除指定了 Id 的訊息。 |
OnPostAnalyzeMessagesAsync |
如果資料庫中有一或多個訊息,則計算每則訊息的平均字數。 |
頁面模型方法會使用 IndexPageTests 類別 (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs) 中的七個測試來進行測試。 測試使用熟悉的 Arrange-Act-Assert 模式。 這些測試著重於:
- 在 ModelState無效時,判斷方法是否遵循正確的行為。
- 確認方法會產生正確的 IActionResult。
- 檢查屬性值指派是否正確。
這個測試群組通常會模擬 DAL 的方法,以針對執行頁面模型方法時的 Act 步驟產生預期的資料。 例如,會模擬 GetMessagesAsync 的 AppDbContext 方法來產生輸出。 當頁面模型方法執行此方法時,模擬會傳回結果。 資料不是來自資料庫。 這會在頁面模型測試中,為使用 DAL 建立可預測的可靠測試條件。
OnGetAsync_PopulatesThePageModel_WithAListOfMessages 測試會顯示如何針對頁面模型模擬 GetMessagesAsync 方法:
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);
在 Act 步驟中執行 OnGetAsync 方法時,它會呼叫頁面模型的 GetMessagesAsync 方法。
單元測試 Act 步驟 (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):
// Act
await pageModel.OnGetAsync();
IndexPage 頁面模型的 OnGetAsync 方法 (src/RazorPagesTestSample/Pages/Index.cshtml.cs):
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
DAL 中的 GetMessagesAsync 方法不會傳回這個方法呼叫的結果。 方法的模擬版本會傳回結果。
在 Assert 步驟中,會從頁面模型的 actualMessages 屬性指派實際訊息 (Messages)。 指派訊息時也會執行類型檢查。 會依 Text 屬性對預期和實際訊息進行比較。 測試斷定這兩個 List<Message> 實例包含相同的訊息。
// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
此群組中的其他測試會建立頁面模型物件,其中包含 DefaultHttpContext、ModelStateDictionary、用於建立 ActionContext 的 PageContext、ViewDataDictionary 和 PageContext。 這些在進行測試時很有用。 例如,訊息應用程式會使用 ModelState 建立 AddModelError 錯誤,以檢查執行 PageResult 時是否傳回有效的 OnPostAddMessageAsync:
[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
// Arrange
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var pageModel = new IndexModel(mockAppDbContext.Object)
{
PageContext = pageContext,
TempData = tempData,
Url = new UrlHelper(actionContext)
};
pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");
// Act
var result = await pageModel.OnPostAddMessageAsync();
// Assert
Assert.IsType<PageResult>(result);
}
其他資源
ASP.NET Core 支援 Razor Pages 應用程式的單元測試。 資料存取層 (DAL) 和頁面模型的測試有助於確保:
- 在應用程式建構期間,Razor 頁面應用程式的元件既能獨立運作,也可作為一個單元。
- 類別和方法的責任範圍有限。
- 另有其他文件說明應用程式應如何運行。
- 回歸是由程式碼更新帶來的錯誤,會在自動化建置和部署過程中發現。
本主題假設您已基本瞭解 Razor Pages 應用程式和單元測試。 如果您不熟悉 Razor Pages 應用程式或測試概念,請參閱下列主題:
檢視或下載範例程式碼 \(英文\) (如何下載)
範例專案是由兩個應用程式所組成:
| App | 專案資料夾 | Description |
|---|---|---|
| 訊息應用程式 | src/RazorPagesTestSample | 允許使用者新增訊息、刪除一則訊息、刪除所有訊息,以及分析訊息 (查明每則訊息的平均字數)。 |
| 測試應用程式 | tests/RazorPagesTestSample.Tests | 用來單元測試訊息應用程式的 DAL 和索引頁面模型。 |
您可以使用 IDE 的內建測試功能,例如 Visual Studio,來執行測試。 如果使用 Visual Studio Code 或命令列,請在 tests/RazorPagesTestSample.Tests 資料夾中的命令提示字元執行下列命令:
dotnet test
訊息應用程式組織
訊息應用程式是具有下列特性的 Razor Pages 訊息系統:
- 應用程式的 [索引] 頁面 (
Pages/Index.cshtml和Pages/Index.cshtml.cs) 提供 UI 和頁面模型方法來控制訊息的新增、刪除和分析 (查明每則訊息的平均字數)。 - 訊息由
Message類別 (Data/Message.cs) 來描述,其中包含兩個屬性:Id(索引鍵) 和Text(訊息)。Text屬性為必要屬性,且限制為 200 個字元。 - 訊息會使用 Entity Framework 的記憶體內部資料庫來儲存†。
- 應用程式在其資料庫內容類別
AppDbContext(Data/AppDbContext.cs) 中包含 DAL。 DAL 方法會標示為virtual,可模擬測試中使用的方法。 - 如果應用程式啟動時資料庫是空的,訊息存放區會以三則訊息初始化。 這些植入的訊息也會用於測試中。
†EF 主題 使用 InMemory 測試,說明如何使用記憶體內部資料庫搭配 MSTest 進行測試。 本主題使用 xUnit 測試架構。 不同測試架構的測試概念和測試實作彼此相似但並不相同。
雖然範例應用程式不使用存放庫模式,也不是工作單位 (UoW) 模式的有效範例,但 Razor Pages 支援此類開發模式。 如需詳細資訊,請參閱設計基礎結構持續性層和測試 ASP.NET Core 中的控制器邏輯 (範例會實作存放庫模式)。
測試應用程式組織
測試應用程式是 tests/RazorPagesTestSample.Tests 資料夾內的主控台應用程式。
| 測試應用程式資料夾 | Description |
|---|---|
| UnitTests |
|
| Utilities | 包含用來為每個 DAL 單元測試建立新資料庫內容選項的 TestDbContextOptions 方法,讓資料庫重設為每個測試的基準條件。 |
資料存取層 (DAL) 的單元測試
訊息應用程式具有 DAL,其中包含 AppDbContext 類別 (src/RazorPagesTestSample/Data/AppDbContext.cs) 中所含的四種方法。 每個方法在測試應用程式中都有一或兩個單元測試。
| DAL 方法 | Function |
|---|---|
GetMessagesAsync |
從依 List<Message> 屬性排序的資料庫取得 Text。 |
AddMessageAsync |
將 Message 加入至資料庫。 |
DeleteAllMessagesAsync |
從資料庫刪除所有 Message 項目。 |
DeleteMessageAsync |
依 Message 從資料庫刪除單一 Id。 |
為每個測試建立新的 DbContextOptions 時,DAL 的單元測試需要 AppDbContext。 為每個測試建立 DbContextOptions 的其中一種方法是使用 DbContextOptionsBuilder:
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
這種方法的問題在於,每個測試都會以先前測試留下的任何狀態接收資料庫。 嘗試撰寫不會互相干擾的不可部分完成單元測試時,這可能會造成問題。 若要強制 AppDbContext 針對每個測試使用新的資料庫內容,請提供以新服務提供者為基礎的 DbContextOptions 實例。 測試應用程式會示範如何使用其 Utilities 類別方法 TestDbContextOptions (tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs) 執行這項操作:
public static DbContextOptions<AppDbContext> TestDbContextOptions()
{
// Create a new service provider to create a new in-memory database.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Create a new options instance using an in-memory database and
// IServiceProvider that the context should resolve all of its
// services from.
var builder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb")
.UseInternalServiceProvider(serviceProvider);
return builder.Options;
}
在 DAL 單元測試中使用 DbContextOptions,可讓每個測試使用全新的資料庫實例以不可部分完成的方式執行:
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
DataAccessLayerTest 類別 (UnitTests/DataAccessLayerTest.cs) 中的每個測試方法都依循類似的 Arrange-Act-Assert 模式:
- Arrange (安排):資料庫已針對測試進行設定,並/或已定義預期的結果。
- Act (作動):執行測試。
- Assert (斷定):做出判斷以確定測試結果是否成功。
例如,DeleteMessageAsync 方法負責移除其 Id (src/RazorPagesTestSample/Data/AppDbContext.cs) 所識別的單一訊息:
public async virtual Task DeleteMessageAsync(int id)
{
var message = await Messages.FindAsync(id);
if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}
這種方法有兩個測試。 其中一項測試會檢查當訊息出現在資料庫中時,此方法是否會刪除該訊息。 另一方法則測試如果要刪除的訊息 Id 不存在,資料庫是否不會變更。
DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound 方法如下所示:
[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
首先,該方法會執行 Arrange 步驟,其間會進行 Act 步驟的安排準備。 已取得植入訊息並保留在 seedMessages 中。 植入訊息已儲存到資料庫中。
Id 為 1 的訊息已完成刪除設定。 執行 DeleteMessageAsync 方法時,預期的訊息應包含除了 Id 為 1 之訊息以外的所有訊息。
expectedMessages 變數代表這個預期的結果。
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
方法開始作動:執行 DeleteMessageAsync 方法,並傳人 recId 的 1:
// Act
await db.DeleteMessageAsync(recId);
最後,該方法從內容取得 Messages,並將其與 expectedMessages 進行比較以斷定兩者相等:
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
為了比較兩個 List<Message> 是否相同:
- 訊息會依
Id排序。 - 訊息組會就
Text屬性進行比較。
類似的測試方法是,DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound 檢查嘗試刪除不存在之訊息的結果。 在此情況下,資料庫中的預期訊息應該等於執行 DeleteMessageAsync 方法之後的實際訊息。 而資料庫的內容應該不會有任何變更:
[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var expectedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(expectedMessages);
await db.SaveChangesAsync();
var recId = 4;
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
頁面模型方法的單元測試
另一組單元測試負責頁面模型方法的測試。 在訊息應用程式中,會在 IndexModel 的 src/RazorPagesTestSample/Pages/Index.cshtml.cs 類別中找到 [索引] 頁面模型。
| 頁面模型方法 | Function |
|---|---|
OnGetAsync |
使用 GetMessagesAsync 方法,從 DAL 取得 UI 的訊息。 |
OnPostAddMessageAsync |
如果 ModelState 有效,則呼叫 AddMessageAsync 將訊息新增至資料庫。 |
OnPostDeleteAllMessagesAsync |
呼叫 DeleteAllMessagesAsync,刪除資料庫中的所有訊息。 |
OnPostDeleteMessageAsync |
執行 DeleteMessageAsync 以刪除指定了 Id 的訊息。 |
OnPostAnalyzeMessagesAsync |
如果資料庫中有一或多個訊息,則計算每則訊息的平均字數。 |
頁面模型方法會使用 IndexPageTests 類別 (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs) 中的七個測試來進行測試。 測試使用熟悉的 Arrange-Act-Assert 模式。 這些測試著重於:
- 在 ModelState無效時,判斷方法是否遵循正確的行為。
- 確認方法會產生正確的 IActionResult。
- 檢查屬性值指派是否正確。
這個測試群組通常會模擬 DAL 的方法,以針對執行頁面模型方法時的 Act 步驟產生預期的資料。 例如,會模擬 GetMessagesAsync 的 AppDbContext 方法來產生輸出。 當頁面模型方法執行此方法時,模擬會傳回結果。 資料不是來自資料庫。 這會在頁面模型測試中,為使用 DAL 建立可預測的可靠測試條件。
OnGetAsync_PopulatesThePageModel_WithAListOfMessages 測試會顯示如何針對頁面模型模擬 GetMessagesAsync 方法:
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);
在 Act 步驟中執行 OnGetAsync 方法時,它會呼叫頁面模型的 GetMessagesAsync 方法。
單元測試 Act 步驟 (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):
// Act
await pageModel.OnGetAsync();
IndexPage 頁面模型的 OnGetAsync 方法 (src/RazorPagesTestSample/Pages/Index.cshtml.cs):
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
DAL 中的 GetMessagesAsync 方法不會傳回這個方法呼叫的結果。 方法的模擬版本會傳回結果。
在 Assert 步驟中,會從頁面模型的 actualMessages 屬性指派實際訊息 (Messages)。 指派訊息時也會執行類型檢查。 會依 Text 屬性對預期和實際訊息進行比較。 測試斷定這兩個 List<Message> 實例包含相同的訊息。
// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
此群組中的其他測試會建立頁面模型物件,其中包含 DefaultHttpContext、ModelStateDictionary、用於建立 ActionContext 的 PageContext、ViewDataDictionary 和 PageContext。 這些在進行測試時很有用。 例如,訊息應用程式會使用 ModelState 建立 AddModelError 錯誤,以檢查執行 PageResult 時是否傳回有效的 OnPostAddMessageAsync:
[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
// Arrange
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var pageModel = new IndexModel(mockAppDbContext.Object)
{
PageContext = pageContext,
TempData = tempData,
Url = new UrlHelper(actionContext)
};
pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");
// Act
var result = await pageModel.OnPostAddMessageAsync();
// Assert
Assert.IsType<PageResult>(result);
}
其他資源
-
以…進行測試
dotnet test - 測試 ASP.NET Core 中的控制器邏輯
- 對程式碼進行單元測試 (Visual Studio)
- ASP.NET Core 中的整合測試
- xUnit.net
- 開始使用 xUnit.net
- Moq
- 最小起訂量快速入門
- JustMockLite:適用於 .NET 開發人員的模擬架構。 (Microsoft 不予維護或支援)