如何用bUnit和C#初始化EditForm与EditContext完成Blazor测试?
如何用bUnit测试Blazor组件中EditForm的OnFieldChanged事件绑定
问题场景
我用Radzen开发Blazor组件,表单核心代码如下:
<EditForm @ref=@_editForm Model="@_selectedItem"> <div class="row"> <div class="col d-flex"> <RadzenDataGrid @ref=@_grid IsLoading=@_isLoading Count=@_totalItems Data=@_items ... TItem="UserItem"> <EmptyTemplate> <p class="datagrid-no-records">No matching results</p> </EmptyTemplate> <Columns> <RadzenDataGridColumn TItem="UserItem" Property="User.Email" Title="User Name" TextAlign="TextAlign.Left"> <Template Context="item"> <RadzenLabel Text=@item.User.Email MouseEnter="@item.OnEmailTooltipShow" MouseLeave="@item.OnTooltipHide"/> </Template> <EditTemplate Context="item"> <CascadingValue Value=@_editForm.EditContext IsFixed="false"> <div class="cell-item"> <RadzenTextBox @bind-Value="item.User.Email" class="w-100 mb-1" @oninput="@item.OnEmailChange" /> <ValidationMessage For="(() => item.User.Email)" /> </div> </CascadingValue> </EditTemplate> </RadzenDataGridColumn> ... </Columns> </RadzenDataGrid> </div> </div> </EditForm>
后台代码:
private List<UserItem> _items = new(); private RadzenDataGrid<UserItem> _grid; private UserDto _selectedItem = new(); private EditForm _editForm; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && _editForm?.EditContext != null) { _editForm.EditContext.OnFieldChanged -= EditContext_OnFieldChanged; _editForm.EditContext.OnFieldChanged += EditContext_OnFieldChanged; } }
我需要用bUnit测试上面OnAfterRenderAsync里的代码块,但当前测试代码:
using var ctx = new TestContext(); ... var userManagementPage = ctx.RenderComponent<UserManagement>();
始终无法进入目标代码块,因为_editForm一直为null,不知道怎么正确初始化模型和EditContext。
解决方法
1. 确保EditForm能生成有效的EditContext
Blazor的EditForm需要绑定非空的Model才能创建EditContext。如果UserDto是一个空对象,可能导致EditContext无法正确初始化。可以在测试中提前给组件的_selectedItem赋值:
using var ctx = new TestContext(); var component = ctx.RenderComponent<UserManagement>(); // 通过反射设置_selectedItem为非空实例(如果组件里没初始化必要属性) var selectedItemField = typeof(UserManagement).GetField("_selectedItem", BindingFlags.NonPublic | BindingFlags.Instance); selectedItemField.SetValue(component.Instance, new UserDto { /* 填充必要属性 */ }); // 重新渲染组件,让EditForm生成EditContext component.Render();
2. 手动绑定_editForm引用并触发OnAfterRenderAsync
因为_editForm是私有字段,测试时需要通过反射获取渲染后的EditForm实例并赋值给它,再手动调用OnAfterRenderAsync:
using var ctx = new TestContext(); var component = ctx.RenderComponent<UserManagement>(); // 先确保组件有数据,EditForm已渲染 var itemsField = typeof(UserManagement).GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance); itemsField.SetValue(component.Instance, new List<UserItem> { new UserItem { User = new User { Email = "test@example.com" } } }); component.Render(); // 获取页面中的EditForm组件 var editForm = component.FindComponent<EditForm>(); // 给组件的_editForm字段赋值 var editFormField = typeof(UserManagement).GetField("_editForm", BindingFlags.NonPublic | BindingFlags.Instance); editFormField.SetValue(component.Instance, editForm.Instance); // 调用OnAfterRenderAsync,传入firstRender=true await component.Instance.OnAfterRenderAsync(true); // 验证事件是否成功绑定 var editContext = editForm.Instance.EditContext; bool isHandlerBound = editContext.OnFieldChanged.GetInvocationList() .Any(d => d.Target == component.Instance && d.Method.Name == "EditContext_OnFieldChanged"); Assert.True(isHandlerBound);
3. 重构组件代码,简化测试
如果不想依赖反射操作私有字段,可以重构组件,把事件绑定逻辑移到EditForm的OnInitialized回调:
<EditForm @ref=@_editForm Model="@_selectedItem" OnInitialized="@BindFieldChangedHandler">
后台代码修改:
private void BindFieldChangedHandler(EditContext editContext) { editContext.OnFieldChanged -= EditContext_OnFieldChanged; editContext.OnFieldChanged += EditContext_OnFieldChanged; }
这样测试时,只要组件渲染,BindFieldChangedHandler就会自动执行,直接验证EditContext的事件绑定即可,无需处理_editForm的引用问题:
using var ctx = new TestContext(); var component = ctx.RenderComponent<UserManagement>(); var editForm = component.FindComponent<EditForm>(); var editContext = editForm.Instance.EditContext; bool isHandlerBound = editContext.OnFieldChanged.GetInvocationList() .Any(d => d.Target == component.Instance && d.Method.Name == "EditContext_OnFieldChanged"); Assert.True(isHandlerBound);
内容的提问来源于stack exchange,提问作者Snake Eyes
相关产品推荐
相关产品推荐

