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

如何测试带特定参数的特定事件是否为多触发事件之一?

问题

我有一个实现了INotifyPropertyChanged接口的对象,直接修改部分属性时会触发变更通知,但修改某一属性可能会导致其他属性作为副作用发生变更,我需要验证这类通知。

示例代码:

public class MyObject : INotifyPropertyChanged
{
    public string MyProperty
    {
        get;
        set; // 为简洁省略通知代码
    }

    public string DependentOnMyProperty
    {
        get; // 基于`MyProperty`更新值
    }
}

var obj = new MyObject();
obj.MyProperty = "Value"; // 应触发属性变更通知:MyProperty和DependentOnMyProperty

我想编写单元测试来检测该依赖属性的事件触发情况。我希望借助测试框架的功能,但不确定怎么实现——比如Assert.Raises()似乎只会报告首个触发的事件,忽略后续的。目前我用的测试代码如下:

[Fact]
public void ComputedProperty_WhenChangingDependentValue_RaisesPropertyChangedEvent()
{
    var obj = new MyObject();
    var hitCounts = new Dictionary<string, int>();
    obj.PropertyChanged += (sender, args) =>
    {
        if (!hitCounts.ContainsKey(args.PropertyName!))
        {
            hitCounts[args.PropertyName] = 0;
        }
        hitCounts[args.PropertyName]++;
    };
    
    obj.MyProperty = "Test Value";
    
    Thread.Sleep(100); // 等待计算属性更新
    
    obj.MyProperty.Should().Be("Test Value");
    obj.DependentOnMyProperty.Should().Be("Value derived from Test Value");
    hitCounts.Should().ContainKey(nameof(MyObject.DependentOnMyProperty)).WhoseValue.Should().Be(1);
}
解决方案

1. 优化现有统计逻辑

你的思路本身没问题,可以简化代码,同时去掉不可靠的Thread.Sleep(如果依赖属性是同步更新的,完全不需要等待;异步场景下也不该用休眠)。

同步场景下的优化版本:

[Fact]
public void ComputedProperty_WhenChangingDependentValue_RaisesPropertyChangedEvent()
{
    var obj = new MyObject();
    var raisedProperties = new List<string>();
    obj.PropertyChanged += (_, args) => raisedProperties.Add(args.PropertyName!);
    
    obj.MyProperty = "Test Value";

    // 验证属性值正确性
    obj.MyProperty.Should().Be("Test Value");
    obj.DependentOnMyProperty.Should().Be("Value derived from Test Value");
    
    // 验证通知的顺序和数量
    raisedProperties.Should().ContainInOrder(nameof(MyObject.MyProperty), nameof(MyObject.DependentOnMyProperty));
    raisedProperties.Count(s => s == nameof(MyObject.DependentOnMyProperty)).Should().Be(1);
}

2. 用FluentAssertions的事件监控简化代码

如果使用FluentAssertions,可以直接借助它的事件监控API,不用自己维护集合:

[Fact]
public void ComputedProperty_WhenChangingDependentValue_RaisesPropertyChangedEvent()
{
    var obj = new MyObject();

    using (obj.MonitorEvents())
    {
        obj.MyProperty = "Test Value";
        
        // 先验证属性值
        obj.MyProperty.Should().Be("Test Value");
        obj.DependentOnMyProperty.Should().Be("Value derived from Test Value");
        
        // 提取所有捕获到的PropertyChanged事件
        var raisedEvents = obj.CapturedEvents.OfType<PropertyChangedEventArgs>().ToList();
        
        // 验证事件触发情况
        raisedEvents.Select(e => e.PropertyName)
            .Should().ContainInOrder(
                nameof(MyObject.MyProperty), 
                nameof(MyObject.DependentOnMyProperty)
            );
        raisedEvents.Count(e => e.PropertyName == nameof(MyObject.DependentOnMyProperty))
            .Should().Be(1);
    }
}

3. 处理异步更新的依赖属性

如果DependentOnMyProperty是异步更新的(比如后台线程计算),绝对不要用Thread.Sleep,改用异步等待机制。比如用FluentAssertions的异步断言:

[Fact]
public async Task ComputedProperty_WhenChangingDependentValue_RaisesPropertyChangedEventAsync()
{
    var obj = new MyObject();
    var raisedProperties = new List<string>();
    obj.PropertyChanged += (_, args) => raisedProperties.Add(args.PropertyName!);
    
    obj.MyProperty = "Test Value";

    // 等待依赖属性的通知被触发,超时时间2秒
    await raisedProperties.Should().Contain(nameof(MyObject.DependentOnMyProperty))
        .AfterWaitingUntil(() => raisedProperties.Contains(nameof(MyObject.DependentOnMyProperty)))
        .WithTimeout(TimeSpan.FromSeconds(2));

    // 验证属性值和事件
    obj.MyProperty.Should().Be("Test Value");
    obj.DependentOnMyProperty.Should().Be("Value derived from Test Value");
}

4. 关于Assert.Raises()的局限

Assert.Raises()本身就是设计用来捕获单个事件的,所以要验证多个事件触发时,必须用捕获所有事件再断言的方式,这也是上面几种方案的核心逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 14:14:50