加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c# – 单元测试async void事件处理程序

发布时间:2020-12-15 22:32:25 所属栏目:百科 来源:网络整理
导读:我在c# winforms中实现了MVP(MVC)模式. 我的视图和演示者如下(没有所有的MVP胶水): public interface IExampleView{ event EventHandlerEventArgs SaveClicked; string Message {get; set; }}public partial class ExampleView : Form{ public event EventH
我在c# winforms中实现了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!"
    }

我正在使用MSTest进行单元测试,以及NSubstitute进行模拟.我想模拟视图中的按钮单击以测试控制器的View_SaveClicked代码,如下所示:

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

    // Act
    View.SaveClicked += Raise.EventWith(new object(),new EventArgs());

    // Assert
    Assert.AreEqual("Saved!",View.Message);
}

我能够使用NSubstitute的Raise.EventWith成功提升View.SaveClicked.但问题是,在Presenter有时间保存消息并且Assert失败之前,代码会立即进入Assert.

我理解为什么会发生这种情况并且设法通过在Assert之前添加Thread.Sleep(500)来解决它,但这不太理想.我也可以更新我的视图来调用presenter.Save()方法,但我希望View尽可能地与Presenter无关.

所以我想知道我可以改进单元测试,以等待异步View_SaveClicked完成或更改View / Presenter代码,以便在这种情况下更容易进行单元测试.

有任何想法吗?

解决方法

由于您只关心单元测试,因此您可以使用自定义SynchronizationContext,它允许您检测async void方法的完成.

您可以使用我的AsyncContext type:

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

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

  // Assert
  Assert.AreEqual("Saved!",View.Message);
}

但是,最好在你自己的代码中使用avoid async void(正如我在关于异步最佳实践的MSDN文章中所描述的那样).我有一篇博文,专门介绍了“async event handlers”的一些方法.

一种方法是替换所有EventHandler< T>.普通代表的事件,并通过等待调用:

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)));
}

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

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读