簡體   English   中英

單元測試async void事件處理程序

[英]Unit testing async void event handler

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

我能夠提高View.SaveClicked成功地利用NSubstitute的Raise.EventWith 然而,問題是,代碼將立即進行Assert演示有時間來保存該消息之前和Assert失敗。

我理解為什么會發生這種情況並且設法通過在Assert之前添加Thread.Sleep(500)來解決它,但這不太理想。 我也可以更新我的視圖來調用presenter.Save()方法,但我希望View盡可能地與Presenter無關。

所以我想知道我可以改進單元測試,以等待async View_SaveClicked完成或更改View / Presenter代碼,以便在這種情況下更容易進行單元測試。

有任何想法嗎?

由於您只關心單元測試,因此您可以使用自定義SynchronizationContext ,它允許您檢測async void方法的完成。

您可以使用我的AsyncContext類型

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

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

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

但是,最好避免在自己的代碼中出現async void (正如我在關於異步最佳實踐的MSDN文章中所描述的那樣)。 我有一篇專門介紹“ 異步事件處理程序 ”的幾種方法的博客文章。

一種方法是用普通委托替換所有EventHandler<T>事件,並通過await調用它:

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 這也不是很好,但對於異步事件處理程序來說更為慣用。

必須對正在運行的任務執行某種類型的工作,並且您需要使用某些東西從任務中返回值。

看起來像Thread.Sleep有助於緩解這種情況,但可能有助於添加一些邏輯,並從任務中獲取值。

來自: https//msdn.microsoft.com/en-us/library/mt674882.aspx

為了完整起見,這里是使用Func而不是事件的工作代碼:

public interface IExampleView
{
    Func<Object, EventArgs, Task> SaveClicked { get; set; }
    string Message { get; set; }
}

public partial class ExampleView : Form
{
    public Func<Object, EventArgs, Task> SaveClicked { get; set; }

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

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

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

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

        View.Message = "Saved!"
    }
}

測試:

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

    // Act
    View.SaveClicked(new object(), new EventArgs()).Wait();

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

要么

[TestMethod]
public async Task WhenSaveButtonClicked_ThenSaveMessageShouldBeShown()
{
    // Arrange

    // Act
    await View.SaveClicked(new object(), new EventArgs());

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM