在单元测试中等待异步事件的触发

13

我试图从单元测试中测试事件 SmtpClient.SendCompleted 的引发,并遇到一个令人烦恼的问题:测试在事件实际触发之前继续处理,导致应用程序终止而没有实际到达事件。因此,请想象以下代码:

[TestClass]
public class emailTest
{
    public bool sentEmail = false;

    [TestMethod]
    public void sendEmail()
    {
        SmtpClient smtp = new SmtpClient("smtpserver");
        smtp.SendCompleted += delegate(Object sender, System.ComponentModel.AsyncCompletedEventArgs e) { sentEmail = true; };
        MailMessage mm = new MailMessage("from@address.com", "to@address.com", "test subject", "test body");
        smtp.SendAsync(mm, "test");
        Assert.IsTrue(sentEmail);
    }
}

如果我手动插入延迟,那么这个测试就会失败...

[TestClass]
public class emailTest
{
    public bool sentEmail = false;

    [TestMethod]
    public void sendEmail()
    {
        SmtpClient smtp = new SmtpClient("smtpserver");
        smtp.SendCompleted += delegate(Object sender, System.ComponentModel.AsyncCompletedEventArgs e) { sentEmail = true; };
        MailMessage mm = new MailMessage("from@address.com", "to@address.com", "test subject", "test body");
        smtp.SendAsync(mm, "test");
        System.Threading.Thread.Sleep(50000); // Manual Delay
        Assert.IsTrue(sentEmail);
    }
}

然后测试通过。

将方法 await smtp.SendAsync 包装为任务似乎并不起作用,因为我实际上并没有等待 SendAsync 完成,而是在尝试等待 SendCompleted 执行完毕后才继续进行测试,但我不确定该如何做。

基于时间的原因,只等待 SendCompleted 处理完成所需的最少时间非常重要。

我进行了大量搜索,但似乎找不到解决此特定问题的任何资料。

快速编辑:在所有情况下,电子邮件都成功发送,只有测试失败。


你测试 SmtpClient 有什么原因吗?这不是你编写的代码,那么测试它的意义何在? - shf301
我不是在测试 SmtpClient,我知道它工作正常。我正在测试自定义处理程序中的代码。 - user3657661
你应该在测试中模拟 SmtpClient,这样你的测试就不会依赖于 SmtpClient 的行为。可以参考 https://dev59.com/1GIj5IYBdhLWcg3ws3Jf 中的示例。 - shf301
创建 SmtpClient 复制品如何帮助我处理实际 SmtpClient 中事件的时序问题? 我不是在测试事件是否触发,也不是在测试 SmtpClient 是否真正执行任何操作。我正在测试事件触发的时间。如果无法确定事件触发的稳定时间,那么我将不得不插入手动延迟,这是次优解。 - user3657661
你的测试不应该基于 SmtpClient 的实际时间。你无法控制它 - 这是框架的实现细节。测试你无法控制的代码是没有意义的。使用模拟 SmtpClient,你可以控制时间(例如立即触发 SendCompleted 事件),以确保你的 SendComplete 处理程序立即运行。 - shf301
1个回答

16

嗯,我必须承认SendCompleted事件只有在SendAsync方法返回后才会被触发听起来有点奇怪...这确实使单元测试更加困难。

但是,如果你想要等待最短的时间,你将不得不引入同步原语。 AutoResetEvent 似乎非常适合这种情况。

// Arrange
var are = new AutoResetEvent(false);

var smtp = new SmtpClient("smtpserver");
smtp.SendCompleted += (s, e) => { are.Set(); };
var mm = new MailMessage("from@address.com", "to@address.com", "test subject", "test body");

// Act
smtp.SendAsync(mm, "test");

// Assert
var wasSignaled = are.WaitOne(timeout: TimeSpan.FromSeconds(1));
Assert.True(wasSignaled);

我刚刚尝试了一下,但没有成功。但是我在 Assert 前面加上了 50 秒的延迟,如果我检查布尔值,则测试通过,但如果我检查 wasSignaled 则不通过。即:smtp.SendCompleted += (s, e) => {are.Set(); sentEmail = true; }; - user3657661
4
@user3657661,你说的“它没起作用”是什么意思?这并没有推动对话向前发展。这个问题已经回答了两次:在这个答案中(可行),以及我申请的重复答案中。 - usr
“它没有起作用”显然是指测试失败了。这个答案显然不起作用,我可以证明。请查看此链接:http://imgur.com/a/XtzVg 如果你声称代码在人们声称它不起作用时客观地起作用,那么你至少应该自己测试一下,这只需要几行代码而已。 - user3657661
3
这段代码中设置的1秒超时时间可能太短了。如果你花点时间理解它在做什么,你可以将超时时间改为无限大。你也没有回复这个重复的消息。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接