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

为单元测试抽象/对接NuGet包中类的技术求助(EAGetMail场景)

解决EAGetMail无接口的Mock方案

一、适配器模式(间接接口化)—— 最推荐方案

因为EAGetMail的核心类(比如MailClient、Mail)都是无接口的具体类,最稳妥的方式是用适配器模式封装一层,让业务代码依赖自定义接口,这样单元测试就能轻松Mock。

步骤1:定义业务所需接口

只暴露你实际用到的方法和属性,不用照搬EAGetMail的全量API:

public interface IEmailClient
{
    List<IEmailMessage> GetUnreadEmails();
    void MarkEmailAsRead(string emailId);
    // 按需添加其他邮件操作方法
}

public interface IEmailMessage
{
    string Id { get; }
    string Subject { get; }
    string PlainTextBody { get; }
    string SenderAddress { get; }
    // 按需添加其他邮件属性
}

步骤2:实现适配器类,包装EAGetMail的具体类

把EAGetMail的调用逻辑封装在适配器里,对外暴露接口:

public class EAGetMailClientAdapter : IEmailClient
{
    private readonly MailClient _innerClient;

    public EAGetMailClientAdapter(MailClient mailClient)
    {
        _innerClient = mailClient;
    }

    public List<IEmailMessage> GetUnreadEmails()
    {
        // 调用EAGetMail原生API,转换为自定义接口实例
        var nativeMails = _innerClient.GetMails(MailQuery.Unread);
        return nativeMails.Select(m => new EAGetMailMessageAdapter(m)).ToList();
    }

    public void MarkEmailAsRead(string emailId)
    {
        var targetMail = _innerClient.FindMail(emailId);
        targetMail.Read = true;
        _innerClient.UpdateMail(targetMail);
    }
}

public class EAGetMailMessageAdapter : IEmailMessage
{
    private readonly Mail _nativeMail;

    public EAGetMailMessageAdapter(Mail nativeMail)
    {
        _nativeMail = nativeMail;
    }

    public string Id => _nativeMail.Uid;
    public string Subject => _nativeMail.Subject;
    public string PlainTextBody => _nativeMail.TextBody;
    public string SenderAddress => _nativeMail.From.Address;
}

步骤3:业务代码依赖接口

让业务类通过构造注入依赖自定义接口,而非EAGetMail的具体类:

public class EmailNotificationService
{
    private readonly IEmailClient _emailClient;

    public EmailNotificationService(IEmailClient emailClient)
    {
        _emailClient = emailClient;
    }

    public void ProcessNewNotifications()
    {
        var unreadEmails = _emailClient.GetUnreadEmails();
        foreach (var email in unreadEmails)
        {
            // 处理邮件通知逻辑
            _emailClient.MarkEmailAsRead(email.Id);
        }
    }
}

步骤4:单元测试Mock接口

用Moq举例,轻松模拟邮件数据和验证方法调用:

[TestClass]
public class EmailNotificationServiceTests
{
    [TestMethod]
    public void ProcessNewNotifications_MarksAllUnreadEmailsAsRead()
    {
        // 准备模拟数据
        var mockEmails = new List<IEmailMessage>
        {
            Mock.Of<IEmailMessage>(m => m.Id == "mail_001"),
            Mock.Of<IEmailMessage>(m => m.Id == "mail_002")
        };

        // Mock邮件客户端接口
        var mockEmailClient = new Mock<IEmailClient>();
        mockEmailClient.Setup(c => c.GetUnreadEmails()).Returns(mockEmails);

        var service = new EmailNotificationService(mockEmailClient.Object);

        // 执行测试
        service.ProcessNewNotifications();

        // 验证每个邮件都被标记为已读
        foreach (var email in mockEmails)
        {
            mockEmailClient.Verify(c => c.MarkEmailAsRead(email.Id), Times.Once);
        }
    }
}

二、其他可选Mock方案

1. 使用商业隔离框架

像TypeMock Isolator、Telerik JustMock这类工具,可以直接Mock非虚方法、密封类和具体类,不用写适配器。但缺点是需要付费,且测试会依赖框架特性,灵活性不如适配器模式。

2. 手写测试替身(Test Double)

如果不想用Mock框架,也可以自己实现测试用的IEmailClient,返回固定模拟数据:

public class FakeEmailClient : IEmailClient
{
    public List<IEmailMessage> MockEmails { get; set; } = new List<IEmailMessage>();
    public List<string> MarkedAsReadIds { get; set; } = new List<string>();

    public List<IEmailMessage> GetUnreadEmails()
    {
        return MockEmails;
    }

    public void MarkEmailAsRead(string emailId)
    {
        MarkedAsReadIds.Add(emailId);
    }
}

测试时直接用这个替身,通过检查MarkedAsReadIds来验证逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 23:20:44