在 ASP.NET Core razor 页单元测试

作者:Luke Latham

ASP.NET Core 支持 Razor 页应用的单元测试。 数据访问层(DAL)和页面模型的测试有助于确保:

  • 在应用程序构建过程中,Razor Pages 应用程序的各个部分将独立工作,并作为一个单元一起工作。
  • 类和方法的责任范围有限。
  • 在应用程序的行为方式上还存在其他文档。
  • 回归是指在自动生成和部署过程中发现的代码更新导致的错误。

本主题假定你基本了解 Razor Pages 应用和单元测试。 如果不熟悉 Razor Pages 应用或测试概念,请参阅以下主题:

查看或下载示例代码如何下载

示例项目包含两个应用:

应用 项目文件夹 描述
消息应用 src/RazorPagesTestSample 允许用户添加消息、删除一条消息、删除所有消息和分析消息(查找每条消息的平均单词数)。
测试应用 tests/RazorPagesTestSample.Tests 用于对消息应用的 DAL 和索引页模型进行单元测试。

可以使用 IDE 的内置测试功能(如Visual StudioVisual Studio for Mac)运行测试。 如果使用Visual Studio Code或命令行,请在 " RazorPagesTestSample " 文件夹中的命令提示符处执行以下命令:

dotnet test

消息应用组织

消息应用是 Razor Pages 的消息系统,具有以下特征:

  • 应用的 "索引" 页(Pages/索引. cshtmlpages/ node.js)提供了一个 UI 和页面模型方法来控制消息的添加、删除和分析(查找每条消息的平均单词数)。
  • 消息由Message类(Data/message .cs)描述,具有两个属性: Id (键)和Text (message)。 Text属性是必需的,并且限制为200个字符。
  • 使用实体框架的内存中数据库†来存储消息。
  • 应用程序在其数据库上下文类AppDbContextData/AppDbContext)中包含 DAL。 DAL 方法被标记virtual为,这允许模拟方法在测试中使用。
  • 如果数据库在应用启动时为空,则会用三条消息初始化消息存储。 这些种子消息还在测试中使用。

†EF 主题使用 InMemory 进行测试说明了如何将内存中数据库用于使用 MSTest 进行测试。 本主题使用xUnit测试框架。 不同测试框架中的测试概念和测试实现相似,但并不完全相同。

尽管该示例应用程序不使用存储库模式,并且不是工作单元(UoW)模式的有效示例,但 Razor Pages 支持这些模式的开发模式。 有关详细信息,请参阅设计基础结构持久性层ASP.NET Core 中的测试控制器逻辑 (示例实现存储库模式)。

测试应用组织

测试应用是 "测试/RazorPagesTestSample " 文件夹中的控制台应用。

测试应用文件夹 描述
UnitTests
  • DataAccessLayerTest.cs包含 DAL 的单元测试。
  • IndexPageTests.cs包含索引页模型的单元测试。
公用 包含用于为每个 DAL 单元测试创建新数据库上下文选项,以便将数据库重置为每个测试的基线条件的方法。TestDbContextOptions

测试框架为xUnit 对象模拟框架为Moq

数据访问层(DAL)的单元测试

消息应用有一个 DAL,其中包含包含在AppDbContext类中的四个方法(src/RazorPagesTestSample/Data/AppDbContext)。 每个方法都在测试应用程序中有一个或两个单元测试。

DAL 方法 函数
GetMessagesAsync 从按Text属性排序的数据库中获取。 List<Message>
AddMessageAsync 将添加Message到数据库中。
DeleteAllMessagesAsync 删除数据库Message中的所有条目。
DeleteMessageAsync Message 删除Id数据库中的单个。

为每个测试创建新DbContextOptions AppDbContext的时,DAL 的单元测试需要。 为每个测试创建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 (test /RazorPagesTestSample/实用工具/实用工具)执行此操作:

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类(run-unittests/DataAccessLayerTest)中的每个测试方法都遵循类似的 "顺序" 操作-断言模式:

  1. 按为测试配置了数据库,并定义了预期的结果。
  2. 意义执行测试。
  3. 断言断言用于确定测试结果是否成功。

例如,该DeleteMessageAsync方法负责删除由其Idsrc/RazorPagesTestSample/Data/AppDbContext)标识的单个消息:

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));
    }
}

首先,方法执行 "排列" 步骤,在该步骤中执行 Act 步骤。 获取并保存seedMessages种子设定消息。 播种消息会保存到数据库中。 设置为Id1消息将被设置为删除。 执行方法时,预期的消息应包含除为Id1消息之外的所有消息。 DeleteMessageAsync 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();

方法的作用:执行方法,并传入recId1DeleteMessageAsync

// 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));
    }
}

页面模型方法的单元测试

另一组单元测试负责页面模型方法的测试。 在 message 应用中,索引页模型IndexModel位于src/RazorPagesTestSample/Pages/ 类中。

页面模型方法 函数
OnGetAsync 使用GetMessagesAsync方法获取来自该 UI 的 DAL 的消息。
OnPostAddMessageAsync 如果ModelState有效,则调用AddMessageAsync将消息添加到数据库。
OnPostDeleteAllMessagesAsync 调用DeleteAllMessagesAsync以删除数据库中的所有消息。
OnPostDeleteMessageAsync 执行DeleteMessageAsync以删除具有指定的Id消息。
OnPostAnalyzeMessagesAsync 如果数据库中有一条或多条消息,则计算每条消息的平均字数。

使用IndexPageTests类中的七个测试(RazorPagesTestSample/run-unittests/IndexPageTests)测试页模型方法。 这些测试使用熟悉的 "排列方式-法" 模式。 这些测试重点关注:

  • 确定在ModelState无效时,方法是否遵循正确的行为。
  • 确认方法生成正确IActionResult
  • 检查是否已正确赋值。

这组测试通常模拟 DAL 的方法,以便为执行页面模型方法的 Act 步骤生成所需的数据。 例如, GetMessagesAsyncAppDbContext方法是模拟,以生成输出。 当页面模型方法执行此方法时,mock 返回结果。 数据不来自数据库。 这会创建可预测、可靠的测试条件,以便在页面模型测试中使用 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 步骤中执行GetMessagesAsync 方法时,它将调用页模型的方法。OnGetAsync

单元测试 Act 步骤(test /RazorPagesTestSample/run-unittests/IndexPageTests):

// Act
await pageModel.OnGetAsync();

IndexPage页模型的OnGetAsync方法(src/RazorPagesTestSample/Pages/ ):

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 PageContext ActionContext的页模型对象,以及用于建立、 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 页应用的单元测试。 数据访问层(DAL)和页面模型的测试有助于确保:

  • 在应用程序构建过程中,Razor Pages 应用程序的各个部分将独立工作,并作为一个单元一起工作。
  • 类和方法的责任范围有限。
  • 在应用程序的行为方式上还存在其他文档。
  • 回归是指在自动生成和部署过程中发现的代码更新导致的错误。

本主题假定你基本了解 Razor Pages 应用和单元测试。 如果不熟悉 Razor Pages 应用或测试概念,请参阅以下主题:

查看或下载示例代码如何下载

示例项目包含两个应用:

应用 项目文件夹 描述
消息应用 src/RazorPagesTestSample 允许用户添加消息、删除一条消息、删除所有消息和分析消息(查找每条消息的平均单词数)。
测试应用 tests/RazorPagesTestSample.Tests 用于对消息应用的 DAL 和索引页模型进行单元测试。

可以使用 IDE 的内置测试功能(如Visual StudioVisual Studio for Mac)运行测试。 如果使用Visual Studio Code或命令行,请在 " RazorPagesTestSample " 文件夹中的命令提示符处执行以下命令:

dotnet test

消息应用组织

消息应用是 Razor Pages 的消息系统,具有以下特征:

  • 应用的 "索引" 页(Pages/索引. cshtmlpages/ node.js)提供了一个 UI 和页面模型方法来控制消息的添加、删除和分析(查找每条消息的平均单词数)。
  • 消息由Message类(Data/message .cs)描述,具有两个属性: Id (键)和Text (message)。 Text属性是必需的,并且限制为200个字符。
  • 使用实体框架的内存中数据库†来存储消息。
  • 应用程序在其数据库上下文类AppDbContextData/AppDbContext)中包含 DAL。 DAL 方法被标记virtual为,这允许模拟方法在测试中使用。
  • 如果数据库在应用启动时为空,则会用三条消息初始化消息存储。 这些种子消息还在测试中使用。

†EF 主题使用 InMemory 进行测试说明了如何将内存中数据库用于使用 MSTest 进行测试。 本主题使用xUnit测试框架。 不同测试框架中的测试概念和测试实现相似,但并不完全相同。

尽管该示例应用程序不使用存储库模式,并且不是工作单元(UoW)模式的有效示例,但 Razor Pages 支持这些模式的开发模式。 有关详细信息,请参阅设计基础结构持久性层ASP.NET Core 中的测试控制器逻辑 (示例实现存储库模式)。

测试应用组织

测试应用是 "测试/RazorPagesTestSample " 文件夹中的控制台应用。

测试应用文件夹 描述
UnitTests
  • DataAccessLayerTest.cs包含 DAL 的单元测试。
  • IndexPageTests.cs包含索引页模型的单元测试。
公用 包含用于为每个 DAL 单元测试创建新数据库上下文选项,以便将数据库重置为每个测试的基线条件的方法。TestDbContextOptions

测试框架为xUnit 对象模拟框架为Moq

数据访问层(DAL)的单元测试

消息应用有一个 DAL,其中包含包含在AppDbContext类中的四个方法(src/RazorPagesTestSample/Data/AppDbContext)。 每个方法都在测试应用程序中有一个或两个单元测试。

DAL 方法 函数
GetMessagesAsync 从按Text属性排序的数据库中获取。 List<Message>
AddMessageAsync 将添加Message到数据库中。
DeleteAllMessagesAsync 删除数据库Message中的所有条目。
DeleteMessageAsync Message 删除Id数据库中的单个。

为每个测试创建新DbContextOptions AppDbContext的时,DAL 的单元测试需要。 为每个测试创建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 (test /RazorPagesTestSample/实用工具/实用工具)执行此操作:

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类(run-unittests/DataAccessLayerTest)中的每个测试方法都遵循类似的 "顺序" 操作-断言模式:

  1. 按为测试配置了数据库,并定义了预期的结果。
  2. 意义执行测试。
  3. 断言断言用于确定测试结果是否成功。

例如,该DeleteMessageAsync方法负责删除由其Idsrc/RazorPagesTestSample/Data/AppDbContext)标识的单个消息:

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));
    }
}

首先,方法执行 "排列" 步骤,在该步骤中执行 Act 步骤。 获取并保存seedMessages种子设定消息。 播种消息会保存到数据库中。 设置为Id1消息将被设置为删除。 执行方法时,预期的消息应包含除为Id1消息之外的所有消息。 DeleteMessageAsync 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();

方法的作用:执行方法,并传入recId1DeleteMessageAsync

// 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));
    }
}

页面模型方法的单元测试

另一组单元测试负责页面模型方法的测试。 在 message 应用中,索引页模型IndexModel位于src/RazorPagesTestSample/Pages/ 类中。

页面模型方法 函数
OnGetAsync 使用GetMessagesAsync方法获取来自该 UI 的 DAL 的消息。
OnPostAddMessageAsync 如果ModelState有效,则调用AddMessageAsync将消息添加到数据库。
OnPostDeleteAllMessagesAsync 调用DeleteAllMessagesAsync以删除数据库中的所有消息。
OnPostDeleteMessageAsync 执行DeleteMessageAsync以删除具有指定的Id消息。
OnPostAnalyzeMessagesAsync 如果数据库中有一条或多条消息,则计算每条消息的平均字数。

使用IndexPageTests类中的七个测试(RazorPagesTestSample/run-unittests/IndexPageTests)测试页模型方法。 这些测试使用熟悉的 "排列方式-法" 模式。 这些测试重点关注:

  • 确定在ModelState无效时,方法是否遵循正确的行为。
  • 确认方法生成正确IActionResult
  • 检查是否已正确赋值。

这组测试通常模拟 DAL 的方法,以便为执行页面模型方法的 Act 步骤生成所需的数据。 例如, GetMessagesAsyncAppDbContext方法是模拟,以生成输出。 当页面模型方法执行此方法时,mock 返回结果。 数据不来自数据库。 这会创建可预测、可靠的测试条件,以便在页面模型测试中使用 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 步骤中执行GetMessagesAsync 方法时,它将调用页模型的方法。OnGetAsync

单元测试 Act 步骤(test /RazorPagesTestSample/run-unittests/IndexPageTests):

// Act
await pageModel.OnGetAsync();

IndexPage页模型的OnGetAsync方法(src/RazorPagesTestSample/Pages/ ):

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 PageContext ActionContext的页模型对象,以及用于建立、 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 中的应用启动

下一篇:Visual Studio 中的 Azure 上的远程调试 ASP.NET Core

关注微信小程序
程序员编程王-随时随地学编程

扫描二维码
程序员编程王

扫一扫关注最新编程教程