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

Fluent-ASsertions ShouldRaisePropertyChangeFor does not work for async Tasks?

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

SimpleNotify class:

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: Object is not being monitored for events or has already been garbage collected. Use the MonitorEvents() extension method to start monitoring events.

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

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

是的,事情就像你说的: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 之后的逻辑可能会在处理完一次性用品后执行。

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

Issue was raised and the work completed just waiting on the documentation to be updated及5.0.0版本即将发布

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

来自更改日志:

{Breaking} Replaced the old thread-unsafe MonitorEvents API with a new Monitor extension method that will return a thread-safe monitoring scope that exposes methods like Should().Raise() and metadata such as OccurredEvents and 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
    }
}