如何在单元测试中模拟/fake SmtpClient?

19

我希望使用它来模拟System.Net.Mail.SmtpClient在MS-Test UnitTest中的行为。因此,我添加了一个System.dll的伪装程序集。然后我创建了一个ShimsContext和一个StubSmtpClient

using (ShimsContext.Create())
{
   StubSmtpClient client = new StubSmtpClient();               
}

但我该怎么做呢? 最终目标是编写一个测试,期望调用send方法时使用MailMessage对象。


你的意思是我也要像其他模拟框架一样做吗?我以为在MS Fakes中不需要这样做。 - user2900970
请阅读有关Shims的内容:http://msdn.microsoft.com/zh-cn/library/hh549175.aspx。 - VikciaR
我尝试过了,但是System.Fakes.ShimSmtpClient并不存在。唯一的Shims是针对GuidDateTime的。 - user2900970
Microsoft Fakes 中的存根只是像其他任何模拟框架一样的标准存根对象。其主要优点是 Fakes 随附于 Visual Studio,为所有内容使用公共委托,并包含 Shims。Shims 的强大之处在于它们允许您覆盖类中的方法,而您无法到达原始存根,但需要它们的代码因此设计不良。 - Magus
另外,如果你的测试是“是否调用了x”,那么你应该重新考虑一下。你的测试不应该知道方法内部除了输入和输出之外的任何东西。测试一个方法内是否有特定代码是不好的,因为它会因为某些有效逻辑变更(如优化)而出错,而这些变更并不会改变输入和输出值。 - Magus
显示剩余3条评论
4个回答

36

您可以创建一个接口,该接口将公开可从SmtpClient使用的函数。

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

然后创建您的具体类,它将执行实际工作。

 public class SmtpClientWrapper : ISmtpClient
    {
        public SmtpClient SmtpClient { get; set; }
        public SmtpClientWrapper(string host, int port)
        {
            SmtpClient = new SmtpClient(host, port);
        }
        public void Send(MailMessage mailMessage)
        {
            SmtpClient.Send(mailMessage);
        }
    }

现在你可以在测试方法中进行模拟,并像这样使用;

[TestMethod()]
        public void SendTest()
        {
            Mock<ISmtpClient> smtpClient = new Mock<ISmtpClient>();
            SmtpProvider smtpProvider = new SmtpProvider(smtpClient.Object);
            string @from = "from@from.com";
            string to = "to@to.com";
            bool send = smtpProvider.Send(@from, to);
            Assert.IsTrue(send);
        }

2
你的SmtpProvider类是从哪里来的?我似乎无法解决它。 - Phillip Copley
3
看起来正在测试的是这个班级。 - Steve Desmond
7
您没有正确处理 SmtpClient - FCin

10

这并不是对你问题的回答,而是另一种方法:

在app.config中设置smtp设置为将邮件作为文件传送到本地目录。然后您可以加载文件并在assert部分检查其内容。

您需要编写更多的代码来进行断言(最好创建一些辅助函数),但您不必执行任何影响生产代码的操作。


2
谢谢,这是一个解决方案。但我认为使用MS Fakes这样的方法不再需要了吗? - user2900970

6
另一种选择是使用nDumbster

Install-Package nDumbster

它在内存中运行Smtp服务器,您可以对其进行验证。这将使其更像集成测试,但通常这些测试比单元测试更有价值,因为您希望测试 SmtpClient 的使用也是正确的。


现代.Net的更新版本faker已经发布,您可以在https://github.com/cmendible/netDumbster找到。 - Payam

2

这篇回答是由Teoman Shipahi撰写的,正确处理SmtpClient对象的版本。

首先让ISmtpClient继承IDisposable

public interface ISmtpClient : IDisposable
{
    void Send(MailMessage mailMessage);
}

然后在SmtpClientWrapper类中实现IDisposable接口:

public class SmtpClientWrapper : ISmtpClient
{
    private bool disposed;
    private readonly SmtpClient smtpClient;

    public SmtpClientWrapper(string host, int port)
    {
        smtpClient = new SmtpClient(host, port);
    }

    ~SmtpClientWrapper()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                smtpClient?.Dispose();
            }
            disposed = true;
        }
    }

    protected void CheckDisposed()
    {
        if (disposed)
        {
            throw new ObjectDisposedException(nameof(SmtpClientWrapper));
        }
    }

    public void Send(MailMessage mailMessage)
    {
        CheckDisposed();
        smtpClient.Send(mailMessage);
    }
}

对于这个版本的 SmtpClientWrapper ,我已经删除了 SmtpClient 属性,以避免在 SmtpClient 属性的setter中替换时 SmtpClient 对象没有被释放的问题。


1
SmtpClient?.Dispose() 可能需要改为 smtpClient?.Dispose()(实例变量应该使用小写的 smtpClient)。 - gotgenes
@gotgenes 谢谢。我已经修改了我的答案。 - Ɖiamond ǤeezeƦ
@ƉiamondǤeezeƦ 为什么不使用 using 语句呢? - Teoman shipahi
1
@ƉiamondǤeezeƦ,你可以在using语句中使用for循环。在Send方法中使用CheckDisposed方法似乎有代码异味。 - Teoman shipahi
消费者应该使用 Using 块来正确地处理客户端的释放。 - Stack Undefined
显示剩余2条评论

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