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

.NET 5中使用xUnit与Moq单元测试FluentEmail返回null如何解决

问题原因及修复方案

核心问题(直接导致sendResponse为null)

你没有对IFluentEmail的SendAsync方法配置Mock返回值,Moq对于未显式Setup的异步方法,默认返回null,所以你执行await SendAsync()后拿到的结果就是空。

其他存在的问题

  • 链式方法UsingTemplateFromFile的Setup参数不匹配:你业务代码调用的是双参数重载,测试Setup时写的三参数重载(第三个是可选的是否母版页参数),可能导致匹配失败,Mock返回默认值。
  • 测试执行阶段错误使用It.IsAny<It.IsAnyType>()作为入参:It.IsAny系列方法仅可用于Moq的Setup和Verify场景,不能作为实际方法调用的入参使用。
  • SendAsync验证时参数匹配错误:SendAsync的入参是CancellationToken类型,你写的SendAsync(null)无法匹配默认调用的SendAsync(CancellationToken.None),会导致验证不通过。
  • 生产配置冗余(不影响单元测试,但会导致线上功能异常):你的ConfigureServices中重复注册了两次SmtpSender,实际运行时只会生效最后一个空配置的SmtpClient,导致生产环境邮件发送失败。

修正后的测试代码

[Fact]
public async Task Can_Verify_FluentEmail_WORKS_Async()
{
    // 准备阶段
    var mockFluentEmail = new Mock<IFluentEmail>();
    // 构造模拟的成功返回结果
    var mockSendResponse = new SendResponse { Successful = true };

    mockFluentEmail.Setup(m => m.To(It.IsAny<string>())).Returns(mockFluentEmail.Object);
    mockFluentEmail.Setup(m => m.Subject(It.IsAny<string>())).Returns(mockFluentEmail.Object);
    // 匹配双参数重载的UsingTemplateFromFile
    mockFluentEmail.Setup(m => m.UsingTemplateFromFile(It.IsAny<string>(), It.IsAny<object>()))
        .Returns(mockFluentEmail.Object);
    // 新增SendAsync的Setup,返回模拟的成功结果
    mockFluentEmail.Setup(m => m.SendAsync(It.IsAny<CancellationToken>()))
        .ReturnsAsync(mockSendResponse);

    // 使用伪造的logger和IFluentEmail实例创建FluentEmail服务
    var fakeLogger = Mock.Of<ILogger<FluentEmailService>>();
    var fluentEmailService = new FluentEmailService(fakeLogger, mockFluentEmail.Object);

    // 执行阶段:替换It.IsAny为实际的测试model,比如随便一个匿名类
    var testModel = new { };
    var sendResponse = await fluentEmailService.SendEmailAsync("Test Subject", "Some Path", testModel, "Some Recipient");
    
    // 断言阶段
    Assert.NotNull(sendResponse);
    Assert.True(sendResponse.Successful);
    mockFluentEmail.Verify(f => f.To("Some Recipient"), Times.Once(), "收件人应为:'Some Recipient'");
    mockFluentEmail.Verify(f => f.Subject("Test Subject"), Times.Once, "主题应为:'Test Subject'");
    mockFluentEmail.Verify(f => f.UsingTemplateFromFile("Some Path", It.IsAny<object>()), Times.Once, "模板路径应为:'Some Path'");
    // 修正SendAsync的参数匹配
    mockFluentEmail.Verify(f => f.SendAsync(It.IsAny<CancellationToken>()), Times.Once, "应触发1次邮件发送");
}

生产配置修正建议

删除重复的AddSmtpSender注册,保留一个有效配置即可:

services.AddFluentEmail("sender@test.com")
        .AddRazorRenderer(@$"{Directory.GetCurrentDirectory()}/Views/")
        .AddSmtpSender("smtp.somecompanyname.com", 25);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 10:57:02