Fluent-ASsertions ShouldRaisePropertyChangeFor 不适用于异步任务?

Posted

技术标签:

【中文标题】Fluent-ASsertions ShouldRaisePropertyChangeFor 不适用于异步任务?【英文标题】:Fluent-ASsertions ShouldRaisePropertyChangeFor does not work for async Tasks? 【发布时间】:2017-04-15 05:42:21 【问题描述】:

我有一个实现INotifyPropertyChanged 的简单类,我在另一个线程上调用属性更改,我很难让FluentAsserts 看到propertyChanged 被调用。如果我在 async Task 方法中使用 Task.Delay 似乎不会发生这种情况。但如果我只是让线程休眠,它就可以了。

SimpleNotify 类:

namespace FluentAssertPropertyThreads

    class SimpleNotify : System.ComponentModel.INotifyPropertyChanged
    
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        private void onChange(string name)
        
            this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(name));
        

        private int count = 0;
        public int Count
        
            get
            
                return this.count;
            

            set
            
                if (this.count != value)
                
                    this.count = value;
                    this.onChange(nameof(this.Count));
                
            
        
    

这是我的单元测试:

using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace FluentAssertPropertyThreads

    [TestClass]
    public class UnitTest1
    
        private SimpleNotify simpleNotify;
        private int notifyCount;
        private void bumpCount()
        
            this.simpleNotify.Count++;
        

        private void SimpleNotify_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        
            SimpleNotify simpleNotify = sender as SimpleNotify;
            if (simpleNotify == null)
            
                throw new ArgumentNullException(nameof(sender), "sender should be " + nameof(SimpleNotify));
            

            if (e.PropertyName.Equals(nameof(SimpleNotify.Count), StringComparison.InvariantCultureIgnoreCase))
            
                this.notifyCount++;
            
        

        [TestInitialize]
        public void TestSetup()
        
            this.notifyCount = 0;
            this.simpleNotify = new SimpleNotify();
            this.simpleNotify.PropertyChanged += SimpleNotify_PropertyChanged;
            this.simpleNotify.MonitorEvents();

            Thread thread = new Thread(this.bumpCount)
            
                IsBackground = true,
                Name = @"My Background Thread",
                Priority = ThreadPriority.Normal
            ;
            thread.Start();
        

        [TestMethod]
        public async Task TestMethod1()
        
            await Task.Delay(100);
            this.notifyCount.Should().Be(1);        //this passes, so I know that my notification has be executed.
            this.simpleNotify.ShouldRaisePropertyChangeFor(x => x.Count);       //but this fails, saying that I need to be monitoring the events (which I am above)
        

        [TestMethod]
        public void TestMethod2()
        
            Thread.Sleep(100);
            this.notifyCount.Should().Be(1);        //this passes, so I know that my notification has be executed.
            this.simpleNotify.ShouldRaisePropertyChangeFor(x => x.Count);       //this passes as I expected
        
    

确切的错误是:

System.InvalidOperationException:对象没有被监视事件或已被垃圾收集。使用 MonitorEvents() 扩展方法开始监控事件。

我不明白 MonitorEvents 会在意我使用 await 还是 Thread.Sleep。我错过了什么?我知道await 离开该方法并返回,而Thread.Sleep 没有。

所以当它在等待期间离开TestMethod1 时,它正在处理FluentAsserts 用来跟踪属性的对象?可以吗?应该是?

【问题讨论】:

【参考方案1】:

是的,事情就像你说的那样:await 暂停当前方法的执行并创建一个状态机以在Delay 完成后返回该方法。但是调用者(一个 UnitTest 引擎)并不期望您的测试是异步的,而是简单地结束执行,从而导致对象的处置。

Stephen Cleary 写了一个出色的 MSDN article about Unit testing and async/await keywords,您可能应该将您的代码移到返回 Task 的方法中,然后等待所有这些都在测试中,如下所示:

async Task Testing()

    await Task.Delay(100);
    this.notifyCount.Should().Be(1);
    this.simpleNotify.ShouldRaisePropertyChangeFor(x => x.Count);


[TestMethod]
public async Task TestMethod1()

    await Testing();

但这仍然可能会失败,因为await 之后的逻辑可能会在一次性处置后执行。

【讨论】:

【参考方案2】:

看起来 5.0.0 版将包括对带有监控的异步测试的支持。

此Issue 已提出,work completed 正在等待文档成为 updated 和版本 5.0.0 发布。

但目前有一个预发布版,代码更改5.0.0-beta2 可以从 NuGet 上的预发布版中获取

来自更改日志:

Breaking 将旧的线程不安全 MonitorEvents API 替换为新的 将返回线程安全监视的监视器扩展方法 暴露诸如 Should().Raise() 和元数据之类的方法的范围 OccurredEvents 和 MonitoredEvents

因此,更新后的 NuGet 代码将如下所示:

[TestInitialize]
public void TestSetup()

    this.notifyCount = 0;
    this.simpleNotify = new SimpleNotify();
    this.simpleNotify.PropertyChanged += SimpleNotify_PropertyChanged;

    Thread thread = new Thread(this.bumpCount)
    
        IsBackground = true,
        Name = @"My Background Thread",
        Priority = ThreadPriority.Normal
    ;
    thread.Start();


[TestMethod]
public async Task TestMethod1()

    using (var MonitoredSimpleNotify = this.simpleNotify.Monitor())
    
        await Task.Delay(100);
        this.notifyCount.Should().Be(1);
        MonitoredSimpleNotify.Should().RaisePropertyChangeFor(x => x.Count); // New API for Monitoring
    

【讨论】:

以上是关于Fluent-ASsertions ShouldRaisePropertyChangeFor 不适用于异步任务?的主要内容,如果未能解决你的问题,请参考以下文章