gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-02 01:09:59 25 4
gpt4 key购买 nike

我已经在 c# winforms 中实现了 MVP (MVC) 模式。

我的 View 和 Presenter 如下(没有所有 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 中模拟按钮单击来测试 Controller 的 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。但是,问题在于,在演示者有时间保存消息之前,代码立即进行到 Assert,并且 Assert 失败。

我理解为什么会发生这种情况,并设法通过在 Assert 之前添加 Thread.Sleep(500) 来解决这个问题,但这并不理想。我还可以更新我的 View 以调用 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>具有普通代表的事件,并通过 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与“延期”。这也不太漂亮,但对于异步事件处理程序来说更惯用。

关于c# - 单元测试 async void 事件处理程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37857716/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com