Provider 的核心逻辑是构造请求、解析响应和转换错误。单元测试通过 Mock HttpMessageHandler 隔离网络,验证这三条路径。
测试结构
public class AliyunSmsServiceTests{ private readonly HttpTestHelper _http = new();
[Fact] public async Task SendAsync_Success_Returns_Ok_Result() { // ① Arrange——配置 Mock 响应 _http.RespondWith(""" {"Code":"OK","RequestId":"req-123","BizId":"biz-456"} """);
var options = new AliyunSmsOptions { AccessKeyId = "test-key", AccessKeySecret = "test-secret", SignName = "Bitzsoft", };
var services = new ServiceCollection(); services.AddAliyunSms(o => { o.AccessKeyId = options.AccessKeyId; o.AccessKeySecret = options.AccessKeySecret; o.SignName = options.SignName; }); // ② 替换 HttpClient 为 Mock services.AddHttpClient<AliyunSmsService>() .ConfigurePrimaryHttpMessageHandler(() => _http.Handler);
var sp = services.BuildServiceProvider(); var sms = sp.GetRequiredService<ISMS>();
// ③ Act var result = await sms.SendAsync(new SmsMessage { To = "13800138000", TemplateId = "SMS_123", TemplateParams = new() { ["code"] = "123456" }, });
// ④ Assert——Result 模式的断言 result.Success.Should().BeTrue(); result.RequestId.Should().Be("req-123"); result.BizId.Should().Be("biz-456"); }
[Fact] public async Task SendAsync_Provider_Error_Returns_Fail_Result() { // ① 模拟厂商返回业务错误 _http.RespondWith(""" {"Code":"isv.BUSINESS_LIMIT_CONTROL","Message":"业务限流"} """);
// ... 注册和获取 service ...
var result = await sms.SendAsync(message);
// ② 断言失败结果的结构化错误码 result.Success.Should().BeFalse(); result.ErrorCode.Should().Be("isv.BUSINESS_LIMIT_CONTROL"); result.ErrorMessage.Should().Contain("业务限流"); }
[Fact] public async Task SendAsync_Network_Error_Returns_Exception_Result() { // ① 模拟网络异常 _http.Throw(new HttpRequestException("Connection refused"));
var result = await sms.SendAsync(message);
// ② 网络错误被捕获并转换为失败 Result result.Success.Should().BeFalse(); result.ErrorCode.Should().Be("EXCEPTION"); }}Result 断言模式
| 断验内容 | 成功路径 | 失败路径 |
|---|---|---|
IsSuccess / Success | Should().BeTrue() | Should().BeFalse() |
Data | Should().NotBeNull() + 字段断言 | Should().BeNull() |
ErrorCode | Should().BeNull() | Should().Be("EXPECTED_CODE") |
ErrorMessage | Should().BeNull() | Should().Contain("expected message") |
HttpTestHelper
public class HttpTestHelper : IDisposable{ public HttpMessageHandlerMock Handler { get; } = new(); public void RespondWith(string json, int statusCode = 200) { ... } public void Throw(Exception ex) { ... } public void Dispose() => Handler.Dispose();}HttpMessageHandlerMock 继承 HttpMessageHandler,通过 ConfigurePrimaryHttpMessageHandler 注入到 HttpClient 管道。