bUnit测试带防抖功能的Search组件时双向绑定失效问题求解
问题根源
- 测试代码未模拟@bind的自动赋值逻辑
你在测试中手动通过Add方法传入Value和ValueChanged时,仅在ValueChanged回调里给myString赋值,没有同步更新StringProperty,所以StringProperty自然不会发生变化。Blazor的@bind-Value语法本质是编译器自动生成了以下逻辑:
Value = Query, ValueChanged = (s) => Query = s
你测试里没写给StringProperty赋值的逻辑,注释的断言必然失败。
- 组件存在不符合Blazor规范的隐藏问题
你在CurrentValue的setter里直接修改了[Parameter]修饰的Value属性,Blazor规范要求子组件不能自行修改父组件传入的参数属性,所有参数变更都应该通过EventCallback通知父组件更新后,由父组件重新传入新值。你当前写法实际运行未出问题是因为防抖触发ValueChanged后,父组件传入的新值和你本地修改的值刚好一致,属于巧合。
修复方案
1. 修复测试代码
要验证双向绑定逻辑,有两种常用写法:
写法1:手动补全ValueChanged的赋值逻辑
private string StringProperty { get; set; } [Fact] public async Task AfterDebounce_ValueUpdates() { var cut = RenderComponent<Search>(parameters => parameters .Add(p => p.Value, StringProperty) .Add(p => p.ValueChanged, (s) => StringProperty = s) ); var input = cut.Find("input"); input.Input("unit test"); Assert.Equal("unit test", cut.Instance.Value); Assert.NotEqual("unit test", StringProperty); await Task.Delay(500); // 防抖时长为350ms,等待500ms足够覆盖 Assert.Equal("unit test", StringProperty); }
写法2:使用bUnit内置的Bind辅助方法,自动模拟@bind行为
[Fact] public async Task AfterDebounce_ValueUpdates() { var stringProperty = ""; var cut = RenderComponent<Search>(parameters => parameters .Bind(p => p.Value, () => stringProperty, v => stringProperty = v) ); var input = cut.Find("input"); input.Input("unit test"); Assert.Equal("unit test", cut.Instance.Value); Assert.NotEqual("unit test", stringProperty); await Task.Delay(500); Assert.Equal("unit test", stringProperty); }
2. 可选:优化组件写法符合Blazor规范
不要直接修改Value参数,本地独立维护输入备份值即可:
public class Search : ComponentBase { [Parameter] public string? Value { get; set; } [Parameter] public EventCallback<string> ValueChanged { get; set; } [DisallowNull] public ElementReference? Element { get; protected set; } private System.Timers.Timer timer = null; // 本地维护当前输入值,不直接修改父组件传入的参数 private string? _currentInputValue; protected override void OnParametersSet() { // 父组件传入新Value时同步本地值 _currentInputValue = Value; base.OnParametersSet(); } protected string? CurrentValue { get => _currentInputValue; set { var hasChanged = !EqualityComparer<string>.Default.Equals(value, _currentInputValue); if (hasChanged) { _currentInputValue = value; DisposeTimer(); timer = new System.Timers.Timer(350); timer.Elapsed += TimerElapsed_TickAsync; timer.Enabled = true; timer.Start(); } } } private void DisposeTimer() { if (timer != null) { timer.Enabled = false; timer.Elapsed -= TimerElapsed_TickAsync; timer.Dispose(); timer = null; } } private async void TimerElapsed_TickAsync( object sender, EventArgs e) { await ValueChanged.InvokeAsync(_currentInputValue); } protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(10, "input"); builder.AddAttribute(20, "type", "text"); builder.AddAttribute(60, "value", BindConverter.FormatValue(CurrentValue)); builder.AddAttribute(70, "oninput", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValue = __value, CurrentValue)); builder.AddElementReferenceCapture(80, __inputReference => Element = __inputReference); builder.CloseElement(); } }
内容的提问来源于stack exchange,提问作者thalacker
相关产品推荐
相关产品推荐

