如何编写Mock SmtpClient的单元测试及解决认证失败问题
解决CommunicationService单元测试中“Authentication unsuccessful”错误
问题根源
- 你的
CommunicationService的SendMail方法完全没有使用构造函数注入的ISmtpClient实例,而是在方法内部直接创建了真实的SmtpClient并调用其Send方法,导致你Mock的ISmtpClient完全失效。代码实际在尝试连接真实的SMTP服务器(smtp.office365.com),从而触发认证失败错误。 - 你定义的
ISmtpClient和SmtpClientWrapper没有被正确利用,依赖注入的设计完全没发挥作用。
修复步骤
重构CommunicationService,使用注入的ISmtpClient
- 删除方法内创建
SmtpClient和SmtpClientWrapper的代码,直接使用构造函数注入的_client实例。 - SMTP配置(Host、Port等)应该通过构造函数注入配置类,而非在业务方法内硬编码。
- 删除方法内创建
调整单元测试,正确Mock ISmtpClient的Send行为
- 无需手动创建测试用的
MailMessage,让Mock匹配任意MailMessage实例,或验证传入的邮件参数符合预期。
- 无需手动创建测试用的
修改后的代码示例
修复后的CommunicationService
public class CommunicationService : ICommunicationService { private readonly ILogger _logger; private readonly ISmtpClient _client; // 可注入SMTP配置类替代硬编码参数 public CommunicationService(ILogger logger, ISmtpClient client) { _logger = logger; _client = client; } public bool SendMail(string mailSender, IEnumerable<string> recipients, IEnumerable<string> cc, string subject, string mailContent, string senderPass) { MailMessage mailMessage = new MailMessage { Subject = subject, Body = mailContent, From = new MailAddress(mailSender, "XXXX"), IsBodyHtml = true }; var recipientsList = recipients.ToList(); if (!recipientsList.Any()) { _logger.LogWarning("Mail not sent, no recipients specified"); return false; // 无收件人直接返回,避免后续无效操作 } foreach (var address in recipientsList) { mailMessage.To.Add(address); } if (cc != null) { foreach (string address in cc) { mailMessage.CC.Add(address); } } try { // 使用注入的ISmtpClient,不再创建真实SmtpClient _client.Send(mailMessage); return true; } catch (Exception exception) { _logger.LogError(exception, exception.Message); return false; } } }
修复后的单元测试
[Test] public void SendMail_SuccessfullySendsMail_ReturnsTrue() { // Arrange var mailSender = "your-email@example.com"; var recipients = new List<string> { "recipient1@example.com", "recipient2@example.com" }; var cc = new List<string> { "cc1@example.com", "cc2@example.com" }; var subject = "Test Subject"; var mailContent = "Test Body"; var senderPass = "your-password"; var loggerMock = new Mock<ILogger<CommunicationService>>(); var clientMock = new Mock<ISmtpClient>(); // 匹配任意MailMessage实例,也可添加参数验证逻辑 clientMock.Setup(x => x.Send(It.IsAny<MailMessage>())).Verifiable(); var service = new CommunicationService(loggerMock.Object, clientMock.Object); // Act var result = service.SendMail(mailSender, recipients, cc, subject, mailContent, senderPass); // Assert Assert.IsTrue(result); clientMock.Verify(x => x.Send(It.IsAny<MailMessage>()), Times.Once); // 验证Send方法被调用一次 }
额外优化建议
- 不要在
SendMail方法内直接创建NetworkCredential,可将认证凭证通过配置类注入到ISmtpClient的实现中。 - 为
SmtpClientWrapper添加SMTP配置的构造参数(Host、Port、Credential等),让配置从外部注入,避免硬编码。 - 单元测试中可添加邮件内容验证,比如检查收件人、主题是否正确:
clientMock.Setup(x => x.Send(It.Is<MailMessage>(m => m.Subject == subject && m.To.Any(a => a.Address == "recipient1@example.com"))));
内容的提问来源于stack exchange,提问作者Bash
相关产品推荐
相关产品推荐

