如何编写单元测试用例和模拟 SmtpClient 发送邮件

How to write unit test case and Mock SmtpClient Send mail

提问人:Bash 提问时间:11/15/2023 最后编辑:Bash 更新时间:11/16/2023 访问量:108

问:

I am writing unit test case for the below class Communication service. I have the below interface communicationservice .
I want to write the Nunit or unit test for the SendMail method which is present in the CommunicationService without sending mails. (smtpClient.Send(mailMessage))


```
public interface ISmtpClient
    {
        void Send(MailMessage message);
    }

    public class SmtpClientWrapper : ISmtpClient
    {
        public SmtpClientWrapper(SmtpClient client)
        {
            Client = client;
        }

        public SmtpClient Client { get; }

        public void Send(MailMessage message)
        {
            Client.Send(message);
        }
    }
    public interface ICommunicationService
    {
        bool SendMail(string mailSender, IEnumerable<string> recipients, IEnumerable<string> cc, string subject, string mailContent, string senderPass);
    }

通信服务参数的实现逻辑 以下是 Mail 消息类所需的详细信息。 用于发送邮件 公共类 CommunicationService:ICommunicationService { private readonly ILogger _logger; private 只读 ISmtpClient _client; 公共通信服务( ILogger 记录器、ISmtpClient 客户端) { _logger = 记录器; _client = 客户端; 另请阅读 AppSettings 了解 SMTP 客户端详细信息 } public bool SendMail(字符串 mailSender, IEnumerable recipients, IEnumerable cc, string subject, string mailContent, string senderPass) { MailMessage mailMessage = 新邮件 { 主题 = 主题, 正文 = mailContent, From = new MailAddress(mailSender, “XXXX”), IsBodyHtml = 真 };

            var recipientsList = recipients.ToList();
            if (!recipientsList.Any())
            {
                _logger.LogWarning("Mail not sent, no recipients specified");
            }

            foreach (var address in recipientsList)
            {
                mailMessage.To.Add(address);
            }

            if (cc != null)
            {
                foreach (string address in cc)
                {
                    mailMessage.CC.Add(address);
                }
            }
            NetworkCredential networkCredential = new NetworkCredential(mailSender, senderPass);


            try
            {

                var smtp = new SmtpClient
                {
                    Host = "smtp.office365.com",
                    Port = 587,
                    EnableSsl = true,
                    UseDefaultCredentials = false,
                    Credentials = networkCredential,
                    DeliveryMethod = SmtpDeliveryMethod.Network,
                    Timeout = 20000,

                };
                var wrapper = new SmtpClientWrapper(smtp);

                smtp.Send(mailMessage);

                return true;
            }

            catch (Exception exception)
            {

                _logger.LogError(exception.Message, exception);
                return false;
            }
        }
    }
From the consumer i call the communication service send method with the details needed for Mail message.Passing here the parameters needed for MailMessage class



here is the code i have tried. Here is the unit test case 



    public void SendMail_SuccessfullySendsMail_ReturnsTrue()
        {
           
            // Arrange
           
            var mailSender = "[email protected]";
            var recipients = new List<string> { "[email protected]", "[email protected]" };
            var cc = new List<string> { "cc[email protected]", "[email protected]" };
            var subject = "Test Subject";
            var mailContent = "Test Body";
            var senderPass = "your-password";
            MailMessage mail = new MailMessage()
            {
                Body = "ABC",
                Subject = subject

            };
            var clientMock = new Mock<ISmtpClient>();
           // clientMock.Setup(x => x.Send(mail));

            // Create an instance of your MailSender class
            var mailSenderInstance = new CommunicationService(logger.Object, clientMock.Object);

            // Act
            var result = mailSenderInstance.SendMail(mailSender, recipients, cc, subject, mailContent, senderPass);



            
          
               Assert.IsTrue(result);


        }



Here is the unit test case, but it not work . Authentication unsuccessful message i am getting from the nunit test case. 
C# asp.net ASP.net-core nunit smtpclient

评论

0赞 vhr 11/15/2023
如果你只是想让“发送”方法成功,你可以使用即最小起订量框架来模拟它 - 这里有另一个关于模拟方法的线程:stackoverflow.com/questions/43649228/...但是你可以重写你的'CommunicationService',以便它接受SmtpClient的实例(在构造函数中)并模拟该实例
0赞 Bash 11/15/2023
但是您可以重写您的“CommunicationService”,以便它接受 SmtpClient 的实例(在构造函数中)并模拟该实例,在 stackoverflow 中看到了这个建议,我已经尝试过了。但是我无法成功使用单元测试用例
0赞 vhr 11/15/2023
我将添加一些代码
0赞 Panagiotis Kanavos 11/15/2023
强烈建议放弃.NET 已弃用 SmtpClient,并使用 .NET 文档中推荐的替代方法 MailKit。MailKit 的 SmtpClient 类继承自该类,这使得模拟变得更加容易ISmtpClient

答:

0赞 vhr 11/15/2023 #1

您可以像这样重写类,并在生产代码中使用 SmtpClientWrapper:

 public interface ISmtpClient
 {
    void Send(MailMessage message);
 }

public class SmtpClientWrapper : ISmtpClient
{
    public SmtpClientWrapper(SmtpClient client)
    {
        Client = client;
    }

    public SmtpClient Client { get; }

    public void Send(MailMessage message)
    {
        Client.Send(message);
    }
}


public class CommunicationService: ICommunicationService 
{ 
    private readonly ILogger _logger; 
    private readonly ISmtpClient _client; 
    public CommunicationService( ILogger logger, ISmtpClient client) 
    { 
        _logger = logger;
        _client = client;
    }

     public bool SendMail(string mailSender, IEnumerable recipients, IEnumerable cc, string subject, string mailContent, string senderPass)
     {
          // validation part of the old method here
          
          // use client from constructor
          _client.Send(mailMessage);
     }
    }

测试方法:

public void SendMail_SuccessfullySendsMail_ReturnsTrue()
    {
         // Arrange
            var clientMock = new Mock<ISmtpClient>();
            clientMock.Setup(x => x.Send(new MailMessage()));

            var mailSender = "[email protected]";
            var recipients = new List<string> { "[email protected]", "[email protected]" };
            var cc = new List<string> { "[email protected]", "[email protected]" };
            var subject = "Test Subject";
            var mailContent = "Test Body";
            var senderPass = "your-password";

            // Create an instance of your MailSender class
            var mailSenderInstance = new CommunicationService(logger.Object, smtpClient.Object);
        
            // Act
            var result = mailSenderInstance.SendMail(mailSender, recipients, cc, subject, mailContent, senderPass);

            // Assert
            Assert.IsTrue(result);

    }
 

还有另一个关于模拟 SmtpClient 的线程(它可能会提供更优雅的解决方案):如何在 UnitTest 中模拟/伪造 SmtpClient?

评论

0赞 Bash 11/15/2023
消息:System.NotSupportedException:不支持的表达式:x => x.Send(mail) 不可重写的成员(此处:SmtpClient.Send)不得用于设置/验证表达式。..我收到此消息
0赞 vhr 11/15/2023
更新了我的答案,以便它使用接口和包装类
0赞 Bash 11/15/2023
主机 =“smtp.office365.com”,端口 = 587,EnableSsl = true,UseDefaultCredentials = false,凭据 = networkCredential,DeliveryMethod = SmtpDeliveryMethod.Network,超时 = 20000 在这种情况下,我怎样才能在这里绑定这些属性
0赞 vhr 11/15/2023
在生产代码中,您可以执行以下操作: var smtp = new SmtpClient{Host = “smtp.office365.com”, Port = 587, EnableSsl = true, UseDefaultCredentials = false, Credentials = networkCredential, DeliveryMethod = SmtpDeliveryMethod.Network, Timeout = 20000};var wrapper = new SmtpClientWrapper (smtp);var service = new CommunicationService(null, wrapper);
0赞 Bash 11/16/2023
尝试激活“xxxxx.xxxxx.xxxxx.xxxxx.xxxxx.Email.SmtpClientWrapper”时,无法解析类型为“System.Net.Mail.SmtpClient”的服务,编译时出现此错误