如何用NSubstitute与xUnit编写含RestSharp Response的集成测试?
关于AttachProviderService的测试问题解答
集成测试 vs 单元测试,不一样
单元测试是隔离测试单个组件,比如只测你的AttachProviderService,所有外部依赖(比如RestClient、配置、日志)都用Mock替代,聚焦验证方法逻辑是否正确。而集成测试是测试多个组件协同工作,比如你的服务要和真实的AdminRead API(或测试环境的API)一起运行,验证整个调用链路是否通顺、数据传递是否正确。简单说:单元测看「代码逻辑对不对」,集成测看「和外部服务连起来能不能正常用」。
能不能用NSubstitute Mock RestClient?
直接Mock RestClient 具体类行不通,因为它没有实现可Mock的接口。但RestSharp提供了IRestClient接口,你得先重构代码,把RestClient改成注入IRestClient,这样就能用NSubstitute Mock了。
先修改你的服务类:
public class AttachProviderService : IAttachProviderService { private readonly ILogger<AttachProviderService> _logger; private readonly string _adminReadApi; private readonly IRestClient _adminReadClient; private readonly int _apiTimeout; // 把原来自己new的RestClient改成注入IRestClient public AttachProviderService(IConfiguration configuration, ILogger<AttachProviderService> logger, IRestClient adminReadClient) { _logger = logger; _adminReadApi = $"{configuration["AdminRead"]}api/SearchRead/"; _adminReadClient = adminReadClient; _apiTimeout = Convert.ToInt32(configuration["Timeout"]); } // GetAttachProviderSearchList方法保持不变 }
重构后,单元测试里就能Mock IRestClient了,示例:
public class AttachProviderServiceTests { [Fact] public async Task GetAttachProviderSearchList_ReturnsData_WhenApiCallSucceeds() { // 准备Mock依赖 var mockLogger = Substitute.For<ILogger<AttachProviderService>>(); var mockConfig = Substitute.For<IConfiguration>(); mockConfig["AdminRead"].Returns("http://test-api.com/"); mockConfig["Timeout"].Returns("30000"); var mockRestClient = Substitute.For<IRestClient>(); var testData = new List<AttachProviderSearchResultViewModel> { new() }; mockRestClient.ExecuteAsync<List<AttachProviderSearchResultViewModel>>(Arg.Any<RestRequest>()) .Returns(new RestResponse<List<AttachProviderSearchResultViewModel>> { Data = testData }); var service = new AttachProviderService(mockConfig, mockLogger, mockRestClient); var searchModel = new AttachProviderSearchViewModel(); // 调用目标方法 var result = await service.GetAttachProviderSearchList(searchModel); // 验证结果 Assert.Equal(testData, result); await mockRestClient.Received(1).ExecuteAsync<List<AttachProviderSearchResultViewModel>>(Arg.Any<RestRequest>()); } }
怎么写集成测试?
集成测试的核心是不Mock外部API,而是调用真实的(或模拟的)测试环境API,验证整个流程是否正常。
方式1:调用真实测试环境API
直接把服务指向测试环境的AdminRead API,实例化真实的RestClient,然后调用方法验证结果:
public class AttachProviderServiceIntegrationTests { private readonly AttachProviderService _service; public AttachProviderServiceIntegrationTests() { // 配置测试用参数,指向测试环境API var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string> { { "AdminRead", "http://test-admin-api.com/" }, { "Timeout", "30000" } }) .Build(); var logger = Substitute.For<ILogger<AttachProviderService>>(); var realRestClient = new RestClient(config["AdminRead"]); _service = new AttachProviderService(config, logger, realRestClient); } [Fact] public async Task GetAttachProviderSearchList_ReturnsExpectedData_FromRealApi() { var searchModel = new AttachProviderSearchViewModel { // 填写测试环境API需要的搜索参数 }; var result = await _service.GetAttachProviderSearchList(searchModel); // 验证结果 Assert.NotNull(result); Assert.NotEmpty(result); // 可根据测试API的预设响应,添加具体字段验证:Assert.Equal("TestName", result.First().Name); } }
方式2:用WireMock模拟API(不依赖真实环境)
如果不想搭建测试环境API,可以用WireMock快速模拟一个临时API服务,返回预设响应:
- 先安装
WireMock.NetNuGet包。 - 编写测试代码:
public class AttachProviderServiceWireMockTests : IAsyncLifetime { private WireMockServer _mockServer; private AttachProviderService _service; public async Task InitializeAsync() { // 启动Mock服务器 _mockServer = WireMockServer.Start(); // 配置Mock的API接口和响应 _mockServer.Given(Request.Create().WithPath("/api/SearchRead/GetAttachProviderSearchList") .WithMethod(HttpMethod.Post)) .RespondWith(Response.Create() .WithStatusCode(HttpStatusCode.OK) .WithBodyAsJson(new List<AttachProviderSearchResultViewModel> { new AttachProviderSearchResultViewModel { Id = 1, Name = "测试服务商" } })); // 配置服务参数,指向Mock服务器地址 var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string> { { "AdminRead", _mockServer.Url }, { "Timeout", "30000" } }) .Build(); var logger = Substitute.For<ILogger<AttachProviderService>>(); var restClient = new RestClient(config["AdminRead"]); _service = new AttachProviderService(config, logger, restClient); } [Fact] public async Task GetAttachProviderSearchList_ReturnsMockedData() { var searchModel = new AttachProviderSearchViewModel(); var result = await _service.GetAttachProviderSearchList(searchModel); Assert.NotNull(result); Assert.Single(result); Assert.Equal(1, result.First().Id); Assert.Equal("测试服务商", result.First().Name); } public async Task DisposeAsync() { _mockServer.Stop(); _mockServer.Dispose(); } }
内容的提问来源于stack exchange,提问作者sudeesh mohan
相关产品推荐
相关产品推荐

