【问题标题】:Bunit 2 way-bindingBunit 2路绑定
【发布时间】:2021-09-10 20:04:54
【问题描述】:

我有一个Search 组件,它实现了一个去抖动计时器,因此它不会调用ValueChanged(因此不会立即更新与其关联的属性)。

我的问题

bUnit 测试似乎不会以两种方式绑定我正在更新的值。

测试代码

private string StringProperty { get; set; }

[Fact]
public async Task AfterDebounce_ValueUpdates()
{
    var myString = "";
    var cut = RenderComponent<Search>(parameters => parameters
            .Add(p => p.Value, StringProperty)
            .Add(p => p.ValueChanged, (s) => myString = s)
        );

    var input = cut.Find("input");
    input.Input("unit test");

    Assert.Equal("unit test", cut.Instance.Value);
    Assert.NotEqual("unit test", myString);
    //Assert.NotEqual("unit test", StringProperty);

    await Task.Delay(5000);

    Assert.Equal("unit test", myString);
    //Assert.Equal("unit test", StringProperty);
}

我原以为注释掉的部分可以工作(因为它们与 ValueChanged 执行相同的操作来更新属性),但它们失败了。

组件

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;
    protected string? CurrentValue {
        get => Value;
        set {
            var hasChanged = !EqualityComparer<string>.Default.Equals(value, Value);
            if (hasChanged)
            {
                Value = 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(Value);
    }

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

如何使用:

可以这样使用,每当Query 更新时,网格就会更新。

<Search @bind-Value=Query />
<Grid Query=@Query />

@code {
    private string? Query { get; set; }
}

这在实践中效果很好,但是在测试时我遇到了问题。

【问题讨论】:

    标签: unit-testing blazor xunit bunit


    【解决方案1】:

    我在我的机器上本地尝试,测试通过了。

    这是您的组件的简化版本,每次值更改仅调用 TimerElapsed_TickAsync 一次,而不是每次计时器用完时(AutoReset 默认为 true),以及两种不同的编写测试的方法,它们都在我的机器上通过:

    public class Search : ComponentBase, IDisposable
    {
        private readonly Timer timer;
    
        [Parameter] public string? Value { get; set; }
        [Parameter] public EventCallback<string> ValueChanged { get; set; }
        [DisallowNull] public ElementReference? Element { get; protected set; }
    
        public Search()
        {
            timer = new Timer(350);
            timer.Elapsed += TimerElapsed_TickAsync;
            timer.Enabled = true;
            timer.AutoReset = false;
        }
    
        protected string? CurrentValue
        {
            get => Value;
            set
            {
                var hasChanged = !EqualityComparer<string>.Default.Equals(value, Value);
                if (hasChanged)
                {
                    RestartTimer();
                    Value = value;
                }
            }
        }
    
        private void RestartTimer()
        {
            if (timer.Enabled)
                timer.Stop();
            timer.Start();
        }
    
        private void TimerElapsed_TickAsync(object sender, EventArgs e) 
            => ValueChanged.InvokeAsync(Value);
    
        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();
        }
    
        public void Dispose() => timer.Dispose();
    }
    

    还有C#版本的测试:

    [Fact]
    public async Task AfterDebounce_ValueUpdates()
    {
        var expected = "test input";
        var count = 0;
        var value = "";
        var cut = RenderComponent<Search>(parameters => parameters
                .Add(p => p.Value, value)
                .Add(p => p.ValueChanged, (s) =>
                {
                    value = s;
                    count++;
                })
            );
    
        cut.Find("input").Input(expected);
    
        await Task.Delay(350);
    
        Assert.Equal(1, count);
        Assert.Equal(expected, value);
    }
    

    以及测试的 .razor 版本(又名。写在 .razor 文件中):

    @inherits TestContext
    @code
    {
        [Fact]
        public async Task AfterDebounce_ValueUpdates()
        {
            var expected = "test input";
            var value = "";
            var cut = Render(@<Search @bind-Value="value" /> );
    
            cut.Find("input").Input(expected);
    
            await Task.Delay(350);
    
            Assert.Equal(expected, value);
        }
    }
    

    【讨论】:

    • 嗨@Egil(喜欢你在Bunit上所做的工作)。我了解剃须刀代码测试有效。但是,对于 C# 版本,您将 ValueChanged 连接到该变量。我不认为我需要这样做。我只是在我的例子中展示了通过连接ValueChanged 我可以让它工作。你不应该只需要.Add(p =&gt; p.Value, StringProperty),然后上面的注释代码就可以工作了吗?
    • 简单来说,我是否需要提供ValueChanged 才能让它工作,还是应该像在普通.razor 中那样自动连接它?
    • 您需要提供一个 ValueChanged 回调。这就是您收到更改通知的方式。当您在 Razor 文件中写入 @bind-Value 时,这也是 Blazor 生成器为您生成的内容。如果不需要提供初始值,则不需要将参数传递给Value 参数。
    猜你喜欢
    • 1970-01-01
    • 2021-10-28
    • 1970-01-01
    • 2017-11-05
    • 1970-01-01
    • 2020-09-19
    • 1970-01-01
    • 1970-01-01
    • 2017-03-11
    相关资源
    最近更新 更多