我刚刚遇到了一个我认为在编写测试时完全可以接受的情况。考虑使用这个辅助类来异步加载某些内容并在操作完成时通过事件进行通知:
public class AsyncItemLoader<T>
{
/// <summary>
/// Handlers will be invoked in registration order, pinky-swear!
/// Also, they are invoked on a thread from the ThreadPool.
/// </summary>
public EventHandler<T> ItemLoaded;
public void LoadAsync()
{
// load the item asynchronously using Task.ContinueWith to fire
// ItemLoaded event to indicate that the item is now available
}
}
现在假设我们在某个模型类中使用这个助手:
public class MyAppModel
{
private readonly AsyncItemLoader<User> userLoader;
public AsyncItemLoader<User> User { get { return userLoader; } }
public MyAppModel()
{
this.userLoader = new AsyncItemLoader<User>(...);
this.userLoader += HandleUserLoaded;
}
public void StartLoadingUser()
{
userLoader.LoadAsync();
}
private void HandleUserLoaded(object sender, User user)
{
// do something with the user here
}
}
好的,现在我们要为 MyAppModel 编写一个测试,该测试依赖于 HandleUserLoaded 在我们调用 StartLoadingUser 之后的某个时间点所做的任何事情,例如检查对模拟依赖项的某些方法的调用。
在我们检查模拟方法的调用之前,我们如何确定地等待 HandleUserLoaded 完成处理?
很容易,因为事件被记录为按注册顺序调用处理程序,并且我们知道 MyAppModel 在客户有机会注册他们的处理程序之前注册了自己的处理程序:
public class MyAppModelTest
{
[Test]
public void StartLoadingUserCausesMyAppModelToDoStuffWithOtherDep()
{
var model = new MyAppModel();
var itemLoaded = new ManualResetEventSlim(initialState: false);
model.User.ItemLoaded += (s, e) => itemLoaded.Set();
model.StartLoadingUser();
itemLoaded.Wait();
// At this point we are guaranteed that MyAppModel.HandleUserLoaded
// has finished execution
myServiceMock.Received().AmazingServiceCall();
}
}