You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在BUnit Blazor单元测试中填充输入框并验证用户创建

Blazor注册页面单元测试:填充输入框与验证用户创建方案

针对你遇到的两个核心问题(InputText输入框无法填充、提交后无法验证用户创建),以下是具体解决方案:

一、解决InputText输入框填充问题

你的测试代码无法填充输入框,是因为操作的是外层.form-group容器,而非InputText内部的原生input元素,且Blazor的@bind-Value默认绑定onchange事件而非oninput。提供两种可行方式:

方式1:定位原生input触发Change事件

直接找到每个.form-group内的<input>标签,调用Change()方法传入测试值:

// 填充Display Name
cut.Find(".form-group:has(label:contains('Display name')) input").Change("Zachrules123");
// 填充Email
cut.Find(".form-group:has(label:contains('Email')) input").Change("Zachrules123@gmail.com");
// 填充Password
cut.Find(".form-group:has(label:contains('Password')) input").Change("password");
// 填充重复密码
cut.Find(".form-group:has(label:contains('Re-enter password')) input").Change("password");

方式2:直接操作InputText组件

通过FindComponent<InputText>定位目标组件,设置Value并触发ValueChanged事件,适合复杂场景:

// 定位Display Name对应的InputText组件
var handleInput = cut.FindComponent<InputText>(x => x.Instance.ValueExpression.Body.ToString().Contains("_handle"));
// 设置组件值并重新渲染
handleInput.SetParametersAndRender(p => p.Add(v => v.Value, "Zachrules123"));
// 触发值变更事件同步到页面绑定字段
await handleInput.Instance.ValueChanged.InvokeAsync("Zachrules123");

二、验证用户是否成功创建

要替代人工查看数据库,需Mock数据库上下文(DbContext),通过验证Mock对象的方法调用来确认用户创建逻辑是否正确执行:

1. 优化注册页面的可测试性

首先确保页面通过构造函数注入DbContext,同时用强类型Model替代object作为EditForm的绑定模型(提升可维护性):

@page "/register"
@inject AppDbContext Context

<h2>Create account</h2>

<EditForm Model="_registerModel" OnSubmit="@Submit">
    <div class="form-group">
        <label>Display name</label>
        <InputText @bind-Value="_registerModel.Handle" class="form-control" />
    </div>
    <div class="form-group">
        <label>Email</label>
        <InputText @bind-Value="_registerModel.Email" type="email" class="form-control" />
    </div>
    <div class="form-group">
        <label>Password</label>
        <InputText @bind-Value="_registerModel.Password" type="password" class="form-control" />
    </div>
    <div class="form-group">
        <label>Re-enter password</label>
        <InputText @bind-Value="_registerModel.RepeatPassword" type="password" class="form-control" />
    </div>
    <div class="d-grid">
        <button type="submit" class="btn btn-primary">Create your account</button>
    </div>
</EditForm>

<div>
<a href="/signin">Sign in</a>
</div>

@code {
    private RegisterModel _registerModel = new();

    private async Task Submit()
    {
        // 省略输入验证逻辑...
        var passwordHash = BCrypt.Net.BCrypt.HashPassword(_registerModel.Password);
        var user = new User
        {
            Id = Guid.NewGuid(),
            Username = _registerModel.Email.ToLowerInvariant(),
            Handle = _registerModel.Handle,
            Password = passwordHash,
            CreatedOn = DateTime.UtcNow
            // 填充其他必要字段
        };

        Context.Users.Add(user);
        await Context.SaveChangesAsync();
    }

    public class RegisterModel
    {
        public string Handle { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public string RepeatPassword { get; set; }
    }
}

2. 编写Mock DbContext的单元测试

使用Moq库Mock DbContext和DbSet,注入到测试上下文后验证方法调用:

using Bunit;
using Moq;
using Microsoft.EntityFrameworkCore;
using YourApp.Data;
using YourApp.Pages;

namespace ZachUnitTests.Pages;

public class RegisterTests
{
    [Test]
    public async Task Submit_CreatesUserWithCorrectData()
    {
        // Arrange
        // Mock DbSet<User>
        var mockUsersSet = new Mock<DbSet<User>>();
        // Mock AppDbContext
        var mockContext = new Mock<AppDbContext>(new DbContextOptions<AppDbContext>());
        mockContext.Setup(c => c.Users).Returns(mockUsersSet.Object);

        using var ctx = new TestContext();
        // 注入Mock的DbContext到测试容器
        ctx.Services.AddScoped(_ => mockContext.Object);

        // Act
        var cut = ctx.RenderComponent<Register>();
        // 填充输入框
        cut.Find(".form-group:has(label:contains('Display name')) input").Change("Zachrules123");
        cut.Find(".form-group:has(label:contains('Email')) input").Change("Zachrules123@gmail.com");
        cut.Find(".form-group:has(label:contains('Password')) input").Change("password");
        cut.Find(".form-group:has(label:contains('Re-enter password')) input").Change("password");
        
        // 点击提交按钮
        await cut.Find("button").ClickAsync();

        // Assert
        // 验证是否添加了符合预期的User对象
        mockUsersSet.Verify(set => set.Add(It.Is<User>(u => 
            u.Handle == "Zachrules123" && 
            u.Username == "zachrules123@gmail.com" &&
            BCrypt.Net.BCrypt.Verify("password", u.Password) // 验证密码哈希正确性
        )), Times.Once);
        // 验证是否调用了SaveChangesAsync
        mockContext.Verify(c => c.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }
}

关键注意事项

  • 必须通过依赖注入获取DbContext,禁止直接实例化,否则无法Mock数据库操作。
  • 用强类型Model替代object作为EditForm的Model,提升绑定效率与测试可维护性。
  • 密码验证需使用哈希库的验证方法(如BCrypt.Verify),而非直接对比哈希字符串。

内容的提问来源于stack exchange,提问作者Zachary McArthur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 22:07:29