C# 单元测试异步void事件处理程序
我已经在WinC窗体中实现了MVP MVC模式 我的视图和演示者如下所示,没有所有MVP胶水: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
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)));
}