业务功能中包含邮件发送,用Smtp4dev测试

前言

网站开发中,经常碰到需要发送邮件的场景。

比如,重置用户密码,需要执行下列流程:

  • 用户在重置页面输入邮箱地址
  • 进入邮箱,使用获得的重置链接打开重置页面
  • 输入新密码.

一般来说,重置链接都需要包含一个token值,用于保证当前重置操作是正确的来源,如下图:

业务功能中包含邮件发送,用Smtp4dev测试

问题

为了将上面的业务实现,示例代码如下:

[ApiController]
[Route("[controller]")]
public class ResetController : ControllerBase
{
    private readonly Dictionary<string,string> _cache= new Dictionary<string, string>();
    [HttpGet]
    public void Get([FromQuery] string email)
    {
        var token = Guid.NewGuid().ToString("N");
        _cache.Add(token, email);
        //发送邮件,smtpServer、from来源于配置
        using (var smtpClient = new SmtpClient(smtpServer,25))
        {
            smtpClient.Send(from, email, "重置密码", token);
        }
    }
    [HttpPost]
    public string Post(string token, string newPassword)
    {
        var email = _cache[token];
        //重置密码逻辑
        return email;
    }
}

可以发现,如果要拿到token,我们必须获得邮件内容。

怎么办?

当然,有很多种解决方案,比如从缓存获取token,但是那样会与业务内部实现强耦合。

在这里,我们用Smtp4dev去真实的获得邮件。

Smtp4dev

Smtp4dev(https://github.com/rnwood/smtp4dev)用于开发和测试的虚拟SMTP电子邮件服务器。可以让你在测试应用程序时,无需向真实客户发送邮件,也无需使用特殊配置设置复杂的真实电子邮件服务器。

安装非常简单,直接执行下列命令:

dotnet tool install -g Rnwood.Smtp4dev

然后执行下列命令运行Smtp4dev:

smtp4dev --urls=https://localhost:9000 --db=d:\codes\smtp4dev.db
  • --urls 管理页面访问地址
  • --db 邮件存储Sqlite数据库,后面测试会用到

smtp4dev对应的SMTP服务器地址为localhost:25

测试用例

测试用例代码很简单:

[Fact]
public void Test1()
{
    var controller = new ResetController();
    var email = "user_"+Guid.NewGuid().ToString("N") + "@localhost";
    controller.Get(email);

    var token = GetToken(email);
    var result = controller.Post(token,"111");
    Assert.Equal(email, result);
}

GetToken将从邮件中获取token。

获取邮件

虽然smtp4dev提供了API来获取邮件,但是我们这里使用更简单的方式,直接从数据库读取邮件。

首先,创建DbContext:

public class Smtp4devContext : DbContext
{
    public DbSet<Message> Messages { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite(@"DataSource=d:\codes\smtp4dev.db");
    }
}
public class Message
{ 
    public string Id { get; set; }
    public string To { get; set; }
    public string ReceivedDate { get; set; }
    public string Data { get; set; }
}

然后,获取指定收件人的最新邮件:

private static Message GetMessage(string email)
{
    return new Smtp4devContext().Messages.Where(p => p.To == email)
        .OrderByDescending(p => p.ReceivedDate).First();
}

最后,从邮件的Data属性中分析出token:

private string GetToken(string email)
{
    var message = GetMessage(email);
    var lines = message.Data.Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
    return lines.Last();
}

运行测试,成功!

结论

如果你在开发和测试时需要发送邮件,试试Smtp4dev吧!