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

如何测试Blazor WASM中的自定义验证组件?

如何测试Blazor WASM的CustomValidation组件

我有一个Blazor WASM项目,准备编写测试用例。作为新手,我想了解如何测试以下CustomValidation组件?

public class CustomValidation : ComponentBase, IDisposable
{
    private IDisposable? _subscriptions;
    private EditContext? _originalEditContext;

    [CascadingParameter] EditContext? CurrentEditContext { get; set; }

    [Parameter] public bool DoEditValidation { get; set; } = false;

    /// <inheritdoc />
    protected override void OnInitialized()
    {
        if (CurrentEditContext == null)
        {
            throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " +
                $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " +
                $"inside an EditForm.");
        }

        _subscriptions = CurrentEditContext.EnableCustomValidation(DoEditValidation, true);
        _originalEditContext = CurrentEditContext;
    }

    public void Dispose()
    {
        _subscriptions?.Dispose();
    }
}

我尝试使用Bunit编写测试:

public class CustomValidationTests : IDisposable
{
    private CustomValidation _customValidation;

    public CustomValidationTests()
    {
        _customValidation = new CustomValidation();
    }

    [Fact]
    public void OnInitialized_SubscribesToCustomValidation()
    {
        // Arrange
        using var ctx = new TestContext();
        var editContext = new EditContext(new object());

        // Act
        var cut = ctx.RenderComponent<CustomValidation>(
            builder => builder.Add(c => c.CurrentEditContext, editContext));

        // Assert
        Assert.NotNull(cut.Instance.CurrentEditContext);
    }
}

但出现错误,原因是CurrentEditContext标记为CascadingParameter属性,存在访问权限问题。


问题原因

直接通过RenderComponent的Add方法传递CascadingParameter是错误用法。CascadingParameter是级联参数,需要通过级联传递的方式提供,而非像普通[Parameter]那样直接赋值。

正确测试方案

方案1:通过TestContext注册级联值

使用TestContext.AddCascadingValue方法注册EditContext,让组件能通过级联获取该参数:

public class CustomValidationTests : IDisposable
{
    [Fact]
    public void OnInitialized_SubscribesToCustomValidation()
    {
        // Arrange
        using var ctx = new TestContext();
        var model = new object();
        var editContext = new EditContext(model);

        // 注册级联的EditContext
        ctx.AddCascadingValue(editContext);

        // Act
        var cut = ctx.RenderComponent<CustomValidation>();

        // Assert
        Assert.NotNull(cut.Instance.CurrentEditContext);
        Assert.Equal(editContext, cut.Instance.CurrentEditContext);
    }

    [Fact]
    public void OnInitialized_ThrowsException_WhenNoEditContext()
    {
        // Arrange
        using var ctx = new TestContext();

        // Act & Assert
        var exception = Assert.Throws<InvalidOperationException>(() => 
            ctx.RenderComponent<CustomValidation>());
        
        Assert.Contains("requires a cascading parameter of type EditContext", exception.Message);
    }

    [Fact]
    public void OnInitialized_UsesDoEditValidationParameter_Correctly()
    {
        // Arrange
        using var ctx = new TestContext();
        var model = new object();
        var editContext = new EditContext(model);
        var expectedDoEditValidation = true;
        bool actualDoEditValidation = false;
        bool actualSecondParam = false;

        // 模拟EnableCustomValidation方法,捕获传入的参数
        IDisposable mockSubscription = Mock.Of<IDisposable>();
        editContext.EnableCustomValidation = (doVal, secondParam) =>
        {
            actualDoEditValidation = doVal;
            actualSecondParam = secondParam;
            return mockSubscription;
        };

        ctx.AddCascadingValue(editContext);

        // Act
        ctx.RenderComponent<CustomValidation>(
            builder => builder.Add(c => c.DoEditValidation, expectedDoEditValidation));

        // Assert
        Assert.Equal(expectedDoEditValidation, actualDoEditValidation);
        Assert.True(actualSecondParam);
    }

    public void Dispose()
    {
        // 清理测试资源
    }
}

方案2:用EditForm包裹组件

EditForm会自动向子组件级联EditContext,直接用EditForm包裹CustomValidation即可提供级联参数:

[Fact]
public void OnInitialized_SubscribesToCustomValidation_WithEditForm()
{
    // Arrange
    using var ctx = new TestContext();
    var model = new object();

    // Act
    var editForm = ctx.RenderComponent<EditForm>(
        builder => builder
            .Add(f => f.Model, model)
            .AddChildContent<CustomValidation>());

    var customValidation = editForm.FindComponent<CustomValidation>();

    // Assert
    Assert.NotNull(customValidation.Instance.CurrentEditContext);
}

注意事项

  • 若需要验证私有字段_subscriptions或_originalEditContext,可选择以下方式:
    1. 将字段改为internal,并在项目文件中添加InternalsVisibleTo配置,允许测试项目访问内部成员;
    2. 使用反射获取私有字段的值;
    3. 通过验证EditContext的行为(比如模拟EnableCustomValidation的调用)间接验证订阅是否成功。
  • 必须测试异常场景,确保组件在缺失级联EditContext时抛出正确的异常。
  • 如果EnableCustomValidation是扩展方法,模拟其行为需要特殊处理,建议将核心逻辑封装到可注入的服务中,方便测试时替换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 07:15:44