You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用MOQ为C# SendEmail方法编写参数校验单元测试

现有代码问题说明

你当前的代码无法直接使用Moq做单元测试,核心原因是方法内部直接实例化了ExchangeService和EmailMessage两个外部依赖的实现类,没有做依赖抽象,Moq无法替换方法内部硬编码创建的实例,需要先做最小改造。

方案1:轻量改造(无需新增接口)

适合老项目快速适配单元测试,通过提取虚方法实现可Mock:

public class EmailService
{
    // 提取ExchangeService创建逻辑为虚方法
    protected virtual ExchangeService CreateExchangeService(string sender, string password)
    {
        var service = new ExchangeService(ExchangeVersion.Exchange2016);
        service.Credentials = new WebCredentials(sender, password);
        return service;
    }

    // 提取EmailMessage创建逻辑为虚方法
    protected virtual EmailMessage CreateEmailMessage(ExchangeService service, string subject, string body, List<string> to, List<string> cc)
    {
        var email = new EmailMessage(service);
        email.ToRecipients.AddRange(to);
        email.CcRecipients.AddRange(cc);
        email.Subject = subject;
        email.Body = new MessageBody(body);
        return email;
    }

    public string SendEmail(string emailNotificationSender, string senderAccountPassword, string emailCcList,string emailBody, string emailSubject, List<string> toReceipients)
    {
        try
        {
            ExchangeService exchangeService = CreateExchangeService(emailNotificationSender, senderAccountPassword);
            exchangeService.AutodiscoverUrl(emailNotificationSender, RedirectionUrlValidationCallback);
            var ccList = emailCcList.Split(';').ToList();
            EmailMessage email = CreateEmailMessage(exchangeService, emailSubject, emailBody, toReceipients, ccList);
            email.Send();
            return "Success";
        }
        catch (Exception exception)
        {
            OnError(new ErrorRecordEventArgs
            {
                ErrorSource = "ExchangeDataStore::ExecuteQuery()",
                Exception = exception
            });
        }
        return default;
    }

    public static bool RedirectionUrlValidationCallback(string redirectionUrl)
    {
        bool result = false;
        Uri redirectionUri = new Uri(redirectionUrl);
        if (redirectionUri.Scheme == "https")
        {
            result = true;
        }
        return result;
    }

    // 你的原有OnError逻辑保留
}

方案2:面向抽象改造(可扩展性更高)

适合新项目或者有重构空间的项目,通过依赖注入解耦:

// 定义Exchange服务工厂接口
public interface IExchangeServiceFactory
{
    ExchangeService Create(string senderEmail, string password);
}

// 定义邮件消息工厂接口
public interface IEmailMessageFactory
{
    EmailMessage Create(ExchangeService service, string subject, string body, List<string> to, List<string> cc);
}

// 改造原有服务类,依赖注入工厂
public class EmailService
{
    private readonly IExchangeServiceFactory _exchangeFactory;
    private readonly IEmailMessageFactory _emailFactory;

    public EmailService(IExchangeServiceFactory exchangeFactory, IEmailMessageFactory emailFactory)
    {
        _exchangeFactory = exchangeFactory;
        _emailFactory = emailFactory;
    }

    public string SendEmail(string emailNotificationSender, string senderAccountPassword, string emailCcList,string emailBody, string emailSubject, List<string> toReceipients)
    {
        try
        {
            ExchangeService exchangeService = _exchangeFactory.Create(emailNotificationSender, senderAccountPassword);
            exchangeService.AutodiscoverUrl(emailNotificationSender, RedirectionUrlValidationCallback);
            var ccList = emailCcList.Split(';').ToList();
            EmailMessage email = _emailFactory.Create(exchangeService, emailSubject, emailBody, toReceipients, ccList);
            email.Send();
            return "Success";
        }
        catch (Exception exception)
        {
            OnError(new ErrorRecordEventArgs
            {
                ErrorSource = "ExchangeDataStore::ExecuteQuery()",
                Exception = exception
            });
        }
        return default;
    }

    // RedirectionUrlValidationCallback和OnError逻辑保留
}

单元测试编写(基于Moq)

这里以方案2为例,方案1的测试逻辑类似,直接MockEmailService的虚方法即可:

using Moq;
using Xunit;
using FluentAssertions;
using Microsoft.Exchange.WebServices.Data;

public class EmailServiceTests
{
    private readonly Mock<IExchangeServiceFactory> _mockExchangeFactory;
    private readonly Mock<IEmailMessageFactory> _mockEmailFactory;
    private readonly EmailService _testService;

    public EmailServiceTests()
    {
        _mockExchangeFactory = new Mock<IExchangeServiceFactory>();
        _mockEmailFactory = new Mock<IEmailMessageFactory>();
        _testService = new EmailService(_mockExchangeFactory.Object, _mockEmailFactory.Object);
    }

    [Fact]
    public void SendEmail_Should_Pass_All_Parameters_Correctly()
    {
        // 1. 构造测试用例参数
        var testSender = "sender@demo.com";
        var testPassword = "DemoPass@123";
        var testCcList = "cc1@demo.com;cc2@demo.com";
        var testBody = "测试邮件正文内容";
        var testSubject = "测试邮件主题";
        var testToList = new List<string> {"receiver1@demo.com", "receiver2@demo.com"};
        var parsedCcList = testCcList.Split(';').ToList();

        // 2. Mock依赖返回
        var mockExchangeService = new Mock<ExchangeService>(ExchangeVersion.Exchange2016);
        _mockExchangeFactory.Setup(f => f.Create(testSender, testPassword))
            .Returns(mockExchangeService.Object);

        var mockEmailMessage = new Mock<EmailMessage>(mockExchangeService.Object);
        mockEmailMessage.Setup(m => m.Send()).Verifiable();
        _mockEmailFactory.Setup(f => f.Create(
                mockExchangeService.Object,
                testSubject,
                testBody,
                testToList,
                parsedCcList
            )).Returns(mockEmailMessage.Object)
            .Verifiable();

        // 3. 执行被测方法
        var result = _testService.SendEmail(testSender, testPassword, testCcList, testBody, testSubject, testToList);

        // 4. 断言验证所有参数符合预期
        result.Should().Be("Success");
        // 验证发件人地址、密码正确传入
        _mockExchangeFactory.Verify(f => f.Create(testSender, testPassword), Times.Once);
        // 验证发件人地址正确传入自动发现方法
        mockExchangeService.Verify(s => s.AutodiscoverUrl(testSender, It.IsAny<AutodiscoverRedirectionUrlValidationCallback>()), Times.Once);
        // 验证主题、正文、收件人、抄送全部正确传入
        _mockEmailFactory.VerifyAll();
        // 验证邮件发送方法被调用
        mockEmailMessage.Verify(m => m.Send(), Times.Once);
    }

    [Fact]
    public void RedirectionUrlValidationCallback_Should_Return_True_Only_For_Https()
    {
        EmailService.RedirectionUrlValidationCallback("https://demo.com").Should().BeTrue();
        EmailService.RedirectionUrlValidationCallback("http://demo.com").Should().BeFalse();
    }
}

说明

  • 你需要验证的发件人地址、抄送列表、邮件正文、发件人账户密码四个参数,已经全部包含在上述测试的校验逻辑中,只要断言全部通过即可证明参数被正确传递
  • 测试需要提前安装NuGet包:Moq、xUnit/NUnit(根据你的测试框架选择)、FluentAssertions(可选,简化断言写法)

内容的提问来源于stack exchange,提问作者Umapathy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 17:48:09