C# 单元测试异步void事件处理程序

C# 单元测试异步void事件处理程序,c#,winforms,unit-testing,events,async-await,C#,Winforms,Unit Testing,Events,Async Await,我已经在WinC窗体中实现了MVP MVC模式 我的视图和演示者如下所示,没有所有MVP胶水: public interface IExampleView { event EventHandler<EventArgs> SaveClicked; string Message {get; set; } } public partial class ExampleView : Form { public event EventHandler<EventAr

我已经在WinC窗体中实现了MVP MVC模式

我的视图和演示者如下所示,没有所有MVP胶水:

public interface IExampleView
{
    event EventHandler<EventArgs> SaveClicked;
    string Message {get; set; }
}

public partial class ExampleView : Form
{
    public event EventHandler<EventArgs> SaveClicked;

    string Message { 
        get { return txtMessage.Text; } 
        set { txtMessage.Text = value; } 
    }

    private void btnSave_Click(object sender, EventArgs e)
    {
        if (SaveClicked != null) SaveClicked.Invoke(sender, e);
    }
}

public class ExamplePresenter
{
    public void OnLoad()
    {
        View.SaveClicked += View_SaveClicked;
    }

    private async void View_SaveClicked(object sender, EventArgs e)
    {
        await Task.Run(() => 
        {
            // Do save
        });

        View.Message = "Saved!"
    }
我能够使用NSubstitute的raise.EventWith成功提升View.SaveClicked。但是,问题是,在演示者有时间保存消息之前,代码立即进入断言,而断言失败

我理解为什么会发生这种情况,并通过在断言之前添加Thread.Sleep500来绕过它,但这并不理想。我还可以更新视图以调用presenter.Save方法,但我希望视图尽可能与presenter无关

因此,我想知道我可以改进单元测试,以等待异步视图完成,或者更改视图/演示者代码,使它们在这种情况下更容易进行单元测试


有什么想法吗?

因为您只关心单元测试,所以您可以使用自定义SynchronizationContext,它允许您检测异步void方法的完成情况

您可以使用my进行以下操作:

[TestMethod]
public void WhenSaveButtonClicked_ThenSaveMessageShouldBeShown()
{
  // Arrange

  AsyncContext.Run(() =>
  {
    // Act
    View.SaveClicked += Raise.EventWith(new object(), new EventArgs());
  });

  // Assert
  Assert.AreEqual("Saved!", View.Message);
}
然而,正如我在一篇关于异步最佳实践的MSDN文章中所描述的那样,最好使用自己的代码。我有一篇博客文章专门介绍了一些方法

一种方法是用普通委托替换所有EventHandler事件,并通过wait调用它:

但是,如果你想要一个真正的活动,这就不那么漂亮了:

使用这种方法,任何同步事件处理程序都需要在处理程序的末尾返回Task.CompletedTask


另一种方法是延迟扩展EventArgs。这也不是很好,但对于异步事件处理程序来说更为惯用。

必须对正在运行的任务执行某种类型的工作,并且您需要使用某种方法从任务返回值

似乎是线程。睡眠有助于缓解这一问题,但可能有助于添加一些逻辑,并从任务中获得价值


From:

AsyncContext看起来很有希望,但需要.NET 4.6,我们使用的是4.5:对于公共Func SaveClicked建议,演示者和单元测试代码会是什么样子?我已经找到了答案。控制器:View.SaveClicked=View\u SaveClicked;私有异步任务视图\u SaveClickedobject发送方,EventArgs e{}单元测试:View.SaveClickednew object,new EventArgs.Wait@兰格斯:不,不要用等待;使用等待。
[TestMethod]
public void WhenSaveButtonClicked_ThenSaveMessageShouldBeShown()
{
  // Arrange

  AsyncContext.Run(() =>
  {
    // Act
    View.SaveClicked += Raise.EventWith(new object(), new EventArgs());
  });

  // Assert
  Assert.AreEqual("Saved!", View.Message);
}
public Func<Object, EventArgs, Task> SaveClicked;
private void btnSave_Click(object sender, EventArgs e)
{
  if (SaveClicked != null) await SaveClicked(sender, e);
}
public delegate Task AsyncEventHandler<T>(object sender, T e);
public event AsyncEventHandler<EventArgs> SaveClicked;
private void btnSave_Click(object sender, EventArgs e)
{
  if (SaveClicked != null)
    await Task.WhenAll(
      SaveClicked.GetInvocationList().Cast<AsyncEventHandler<T>>
          .Select(x => x(sender, e)));
}