ASP.NET Core での Razor Pages の単体テスト
ASP.NET Core では Razor Pages アプリの単体テストをサポートしています。 データ アクセス層 (DAL) とページ モデルのテストによって、次のことを保証できます。
- Razor Pages アプリの一部は、アプリの構築時に独立して、または 1 つの単位として連携して機能します。
- クラスとメソッドの担当のスコープは限定されています。
- アプリの動作に関する追加のドキュメントがあります。
- コードの更新によって発生したエラーである回帰は、自動ビルドおよびデプロイ時に検出されます。
このトピックでは、Razor Pages アプリと単体テストの基本的な知識があることを前提としています。 Razor Pages アプリまたはテストの概念になじみがない場合は、次のトピックを参照してください。
- ASP.NET Core での Razor ページの概要
- チュートリアル: ASP.NET Core の Razor Pages の概要
- dotnet テストと xUnit を使用した .NET Core での単体テスト C#
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
サンプル プロジェクトは、次の 2 つのアプリで構成されています。
アプリ | プロジェクト フォルダー | 説明 |
---|---|---|
メッセージ アプリ | src/RazorPagesTestSample | ユーザーがメッセージの追加、1 つのメッセージの削除、すべてのメッセージの削除、およびメッセージの分析 (メッセージあたりの平均単語数の検出) をできるようにします。 |
アプリをテストする | tests/RazorPagesTestSample.Tests | メッセージ アプリの DAL およびインデックス ページ モデルの単体テストに使用されます。 |
テストは、Visual Studio などの IDE に組み込まれているテスト機能を使用して実行できます。 Visual Studio Code またはコマンド ラインを使用する場合は、コマンドプロンプトで tests/RazorPagesTestSample フォルダー内の次のコマンドを実行します。
dotnet test
メッセージ アプリの構成
メッセージ アプリは、次の特性を持つ Razor Pages メッセージ システムです。
- アプリのインデックス ページ (
Pages/Index.cshtml
とPages/Index.cshtml.cs
) には、メッセージの追加、削除、および分析 (メッセージあたりの平均単語数の検出) を制御する UI およびページ モデル メソッドが用意されています。 - メッセージは、
Id
(キー) とText
(メッセージ) の 2 つのプロパティを持つMessage
クラス (Data/Message.cs
) によって記述されます。Text
プロパティは必須であり、200 文字までに制限されています。 - メッセージは、Entity Framework のメモリ内データベース† を使用して格納されます。
- アプリでは、データベース コンテキスト クラス
AppDbContext
(Data/AppDbContext.cs
) 内に DAL を格納します。 DAL メソッドはvirtual
とマークされ、これにより、テストで使用するためのメソッドのモックを作成できます。 - アプリの起動時にデータベースが空の場合、メッセージ ストアが 3 つのメッセージで初期化されます。 これらのシードされたメッセージも、テストで使用されます。
†InMemory を使用したテストに関する EF トピックでは、MSTest を使用したテストでメモリ内データベースを使用する方法について説明しています。 このトピックでは、xUnit テスト フレームワークを使用します。 テストの概念とテストの実装は、異なるテスト フレームワークでも類似していますが、同一ではありません。
サンプル アプリではリポジトリ パターンを使用しておらず、Unit of Work (UoW) パターンの有効な例ではありませんが、Razor Pages ではこれらの開発パターンをサポートしています。 詳細については、「インフラストラクチャの永続レイヤーの設計」およびASP.NET Core でのコントローラー ロジックのテストに関する記事を参照してください (このサンプルでは、リポジトリ パターンを実装しています)。
テスト アプリの構成
テスト アプリは、tests/RazorPagesTestSample.Tests フォルダー内のコンソールアプリです。
テスト アプリ フォルダー | 説明 |
---|---|
UnitTests |
|
Utilities | データベースが各テストのベースライン条件にリセットされるように、各 DAL 単体テストの新しいデータベース コンテキスト オプションを作成するために使用する TestDbContextOptions メソッドが含まれています。 |
テスト フレームワークは、xUnit です。 オブジェクトのモック フレームワークは Moq です。
データアクセス層 (DAL) の単体テスト
メッセージ アプリには、AppDbContext
クラス (src/RazorPagesTestSample/Data/AppDbContext.cs
) に含まれる 4 つのメソッドを持つ DAL があります。 テスト アプリで、各メソッドには 1 つか 2 つの単体テストがあります。
DAL メソッド | 関数 |
---|---|
GetMessagesAsync |
データベースから Text プロパティで並べ替えられた List<Message> を取得します。 |
AddMessageAsync |
Message をデータベースに追加します。 |
DeleteAllMessagesAsync |
データベースからすべての Message エントリを削除します。 |
DeleteMessageAsync |
Id によって、データベースから 1 つの Message を削除します。 |
DAL の単体テストでは、各テストの新しい AppDbContext
を作成する際に、DbContextOptions が必要です。 各テストの DbContextOptions
を作成する 1 つの方法として、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
によって識別された 1 つのメッセージの削除を担当します (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();
}
}
このメソッドには 2 つのテストがあります。 1 つのテストでは、メッセージがデータベースに存在する場合に、このメソッドによってメッセージが削除されます。 他方のメソッドでは、削除対象のメッセージ 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
メソッドは 1
の recId
を渡して実行されます。
// Act
await db.DeleteMessageAsync(recId);
最後に、メソッドはコンテキストから Messages
を取得し、expectedMessages
と比較して、2 つが等しいことをアサートします。
// 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));
2 つの 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));
}
}
ページ モデル メソッドの単体テスト
別の単体テスト セットは、ページ モデル メソッドのテストを担当します。 メッセージ アプリで、インデックス ページ モデルは、src/RazorPagesTestSample/Pages/Index.cshtml.cs
の IndexModel
クラスにあります。
ページ モデル メソッド | 関数 |
---|---|
OnGetAsync |
GetMessagesAsync メソッドを使用して、UI の DAL からメッセージを取得します。 |
OnPostAddMessageAsync |
ModelState が有効な場合、AddMessageAsync を呼び出して、データベースにメッセージを追加します。 |
OnPostDeleteAllMessagesAsync |
データベース内のすべてのメッセージを削除するには、DeleteAllMessagesAsync を呼び出します。 |
OnPostDeleteMessageAsync |
Id を指定してメッセージを削除するには、DeleteMessageAsync を実行します。 |
OnPostAnalyzeMessagesAsync |
データベースに 1 つ以上のメッセージが含まれている場合、メッセージあたりの平均単語数を計算します。 |
ページ モデル メソッドは、IndexPageTests
クラス (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
) の 7 つのテストを使用してテストされます。 テストでは、おなじみの Arrange-Assert-Act パターンを使用します。 これらのテストでは次に焦点を合わせています。
- ModelState が無効な場合に、メソッドが正しい動作に従うかどうかを判断します。
- メソッドによって正しい IActionResult が生成されることを確認します。
- プロパティ値の割り当てが正しく行われていることを確認します。
この一連のテストでは、多くの場合に、ページ モデル メソッドが実行される Act ステップ用に予期されるデータを生成するために、DAL のメソッドのモックを作成します。 たとえば、AppDbContext
の GetMessagesAsync
メソッドは、出力を生成するためにモックが作成されます。 ページ モデル メソッドでこのメソッドを実行すると、モックによって結果が返されます。 データはデータベースから取得されません。 これにより、ページ モデル テストで 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
プロパティによって比較されます。 テストでは、2 つの 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、PageContext
を確立するための ActionContext、ViewDataDictionary
、および PageContext
を含むページ モデル オブジェクトが作成されます。 これらは、テストの実施に役立ちます。 たとえば、メッセージ アプリでは、OnPostAddMessageAsync
の実行時に有効な PageResult が返されることを確認するために、AddModelError を使用して ModelState
エラーを作成します。
[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 Pages アプリの一部は、アプリの構築時に独立して、または 1 つの単位として連携して機能します。
- クラスとメソッドの担当のスコープは限定されています。
- アプリの動作に関する追加のドキュメントがあります。
- コードの更新によって発生したエラーである回帰は、自動ビルドおよびデプロイ時に検出されます。
このトピックでは、Razor Pages アプリと単体テストの基本的な知識があることを前提としています。 Razor Pages アプリまたはテストの概念になじみがない場合は、次のトピックを参照してください。
- ASP.NET Core での Razor ページの概要
- チュートリアル: ASP.NET Core の Razor Pages の概要
- dotnet テストと xUnit を使用した .NET Core での単体テスト C#
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
サンプル プロジェクトは、次の 2 つのアプリで構成されています。
アプリ | プロジェクト フォルダー | 説明 |
---|---|---|
メッセージ アプリ | src/RazorPagesTestSample | ユーザーがメッセージの追加、1 つのメッセージの削除、すべてのメッセージの削除、およびメッセージの分析 (メッセージあたりの平均単語数の検出) をできるようにします。 |
アプリをテストする | tests/RazorPagesTestSample.Tests | メッセージ アプリの DAL およびインデックス ページ モデルの単体テストに使用されます。 |
テストは、Visual Studio などの IDE に組み込まれているテスト機能を使用して実行できます。 Visual Studio Code またはコマンド ラインを使用する場合は、コマンドプロンプトで tests/RazorPagesTestSample フォルダー内の次のコマンドを実行します。
dotnet test
メッセージ アプリの構成
メッセージ アプリは、次の特性を持つ Razor Pages メッセージ システムです。
- アプリのインデックス ページ (
Pages/Index.cshtml
とPages/Index.cshtml.cs
) には、メッセージの追加、削除、および分析 (メッセージあたりの平均単語数の検出) を制御する UI およびページ モデル メソッドが用意されています。 - メッセージは、
Id
(キー) とText
(メッセージ) の 2 つのプロパティを持つMessage
クラス (Data/Message.cs
) によって記述されます。Text
プロパティは必須であり、200 文字までに制限されています。 - メッセージは、Entity Framework のメモリ内データベース† を使用して格納されます。
- アプリでは、データベース コンテキスト クラス
AppDbContext
(Data/AppDbContext.cs
) 内に DAL を格納します。 DAL メソッドはvirtual
とマークされ、これにより、テストで使用するためのメソッドのモックを作成できます。 - アプリの起動時にデータベースが空の場合、メッセージ ストアが 3 つのメッセージで初期化されます。 これらのシードされたメッセージも、テストで使用されます。
†InMemory を使用したテストに関する EF トピックでは、MSTest を使用したテストでメモリ内データベースを使用する方法について説明しています。 このトピックでは、xUnit テスト フレームワークを使用します。 テストの概念とテストの実装は、異なるテスト フレームワークでも類似していますが、同一ではありません。
サンプル アプリではリポジトリ パターンを使用しておらず、Unit of Work (UoW) パターンの有効な例ではありませんが、Razor Pages ではこれらの開発パターンをサポートしています。 詳細については、「インフラストラクチャの永続レイヤーの設計」およびASP.NET Core でのコントローラー ロジックのテストに関する記事を参照してください (このサンプルでは、リポジトリ パターンを実装しています)。
テスト アプリの構成
テスト アプリは、tests/RazorPagesTestSample.Tests フォルダー内のコンソールアプリです。
テスト アプリ フォルダー | 説明 |
---|---|
UnitTests |
|
Utilities | データベースが各テストのベースライン条件にリセットされるように、各 DAL 単体テストの新しいデータベース コンテキスト オプションを作成するために使用する TestDbContextOptions メソッドが含まれています。 |
テスト フレームワークは、xUnit です。 オブジェクトのモック フレームワークは Moq です。
データアクセス層 (DAL) の単体テスト
メッセージ アプリには、AppDbContext
クラス (src/RazorPagesTestSample/Data/AppDbContext.cs
) に含まれる 4 つのメソッドを持つ DAL があります。 テスト アプリで、各メソッドには 1 つか 2 つの単体テストがあります。
DAL メソッド | 関数 |
---|---|
GetMessagesAsync |
データベースから Text プロパティで並べ替えられた List<Message> を取得します。 |
AddMessageAsync |
Message をデータベースに追加します。 |
DeleteAllMessagesAsync |
データベースからすべての Message エントリを削除します。 |
DeleteMessageAsync |
Id によって、データベースから 1 つの Message を削除します。 |
DAL の単体テストでは、各テストの新しい AppDbContext
を作成する際に、DbContextOptions が必要です。 各テストの DbContextOptions
を作成する 1 つの方法として、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
によって識別された 1 つのメッセージの削除を担当します (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();
}
}
このメソッドには 2 つのテストがあります。 1 つのテストでは、メッセージがデータベースに存在する場合に、このメソッドによってメッセージが削除されます。 他方のメソッドでは、削除対象のメッセージ 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
メソッドは 1
の recId
を渡して実行されます。
// Act
await db.DeleteMessageAsync(recId);
最後に、メソッドはコンテキストから Messages
を取得し、expectedMessages
と比較して、2 つが等しいことをアサートします。
// 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));
2 つの 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));
}
}
ページ モデル メソッドの単体テスト
別の単体テスト セットは、ページ モデル メソッドのテストを担当します。 メッセージ アプリで、インデックス ページ モデルは、src/RazorPagesTestSample/Pages/Index.cshtml.cs
の IndexModel
クラスにあります。
ページ モデル メソッド | 関数 |
---|---|
OnGetAsync |
GetMessagesAsync メソッドを使用して、UI の DAL からメッセージを取得します。 |
OnPostAddMessageAsync |
ModelState が有効な場合、AddMessageAsync を呼び出して、データベースにメッセージを追加します。 |
OnPostDeleteAllMessagesAsync |
データベース内のすべてのメッセージを削除するには、DeleteAllMessagesAsync を呼び出します。 |
OnPostDeleteMessageAsync |
Id を指定してメッセージを削除するには、DeleteMessageAsync を実行します。 |
OnPostAnalyzeMessagesAsync |
データベースに 1 つ以上のメッセージが含まれている場合、メッセージあたりの平均単語数を計算します。 |
ページ モデル メソッドは、IndexPageTests
クラス (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
) の 7 つのテストを使用してテストされます。 テストでは、おなじみの Arrange-Assert-Act パターンを使用します。 これらのテストでは次に焦点を合わせています。
- ModelState が無効な場合に、メソッドが正しい動作に従うかどうかを判断します。
- メソッドによって正しい IActionResult が生成されることを確認します。
- プロパティ値の割り当てが正しく行われていることを確認します。
この一連のテストでは、多くの場合に、ページ モデル メソッドが実行される Act ステップ用に予期されるデータを生成するために、DAL のメソッドのモックを作成します。 たとえば、AppDbContext
の GetMessagesAsync
メソッドは、出力を生成するためにモックが作成されます。 ページ モデル メソッドでこのメソッドを実行すると、モックによって結果が返されます。 データはデータベースから取得されません。 これにより、ページ モデル テストで 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
プロパティによって比較されます。 テストでは、2 つの 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、PageContext
を確立するための ActionContext、ViewDataDictionary
、および PageContext
を含むページ モデル オブジェクトが作成されます。 これらは、テストの実施に役立ちます。 たとえば、メッセージ アプリでは、OnPostAddMessageAsync
の実行時に有効な PageResult が返されることを確認するために、AddModelError を使用して ModelState
エラーを作成します。
[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 テストと xUnit を使用した .NET Core での単体テスト C#
- ASP.NET Core のコントローラーのロジックをテストする
- コードの単体テスト (Visual Studio)
- ASP.NET Core での統合テスト
- xUnit.net
- XUnit.net の概要:.NET SDK コマンド ラインでの .Net Core の使用
- Moq
- Moq クイック スタート
- JustMockLite:.NET 開発者向けのモック フレームワーク。 ("Microsoft では保守管理もサポートも行っていません。 ")
ASP.NET Core