如何在xUnit测试中Mock内部实例化新对象的方法 以RestClient为例
xUnit中Mock内部实例化的RestClient的方法
你当前代码无法直接Mock RestClient的根本原因是:方法内部直接通过new RestClient(_baseUrl)创建实例,属于硬编码的强依赖,常规Mock框架无法拦截替换这个内部创建的对象,需要先做少量解耦重构,再编写测试即可。
步骤1:解耦RestClient实例化逻辑
推荐用抽象工厂模式做解耦,兼容不同BaseUrl的场景:
1.1 定义工厂接口
public interface IRestClientFactory { IRestClient Create(string baseUrl); }
1.2 实现默认工厂
public class RestClientFactory : IRestClientFactory { public IRestClient Create(string baseUrl) => new RestClient(baseUrl); }
1.3 修改业务代码
首先在业务类的构造函数中注入工厂:
public class 你的业务服务类 { private readonly ICognitoPoolService _cognitoPoolService; private readonly string _baseUrl; private readonly string _getbEndpoint; private readonly IRestClientFactory _restClientFactory; // 构造函数注入工厂 public 你的业务服务类(ICognitoPoolService cognitoPoolService, IConfiguration configuration, IRestClientFactory restClientFactory) { _cognitoPoolService = cognitoPoolService; _baseUrl = configuration["RestClient:BaseUrl"]; _getbEndpoint = configuration["RestClient:GetUserEndpoint"]; _restClientFactory = restClientFactory; } public async Task<TaUser> GetUser(string username) { string token = await _cognitoPoolService.GetToken(); // 把原来的直接new改成从工厂获取实例 IRestClient client = _restClientFactory.Create(_baseUrl); RestRequest request = new RestRequest(resource: _getbEndpoint, method: Method.POST); var requestBody = new GetTaUserRequest() { Username = username }; request.AddJsonBody(JsonConvert.SerializeObject(requestBody)); request.AddHeaders(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("Authorization", $"Bearer {token}") }); IRestResponse response = await client.ExecutePostAsync(request); var user = JsonConvert.DeserializeObject<IdentityProviderUser>(response.Content); if (user.Outcome.StatusCode == 200) return user.User; else throw new Exception($"{user.Outcome.Message} "); } }
然后在你的依赖注入容器里注册工厂即可,比如ASP.NET Core中:
services.AddSingleton<IRestClientFactory, RestClientFactory>();
步骤2:编写xUnit测试用例
我们使用Moq作为Mock框架,需要先安装对应的NuGet包:Moq、xunit、xunit.runner.visualstudio
using Moq; using Xunit; using RestSharp; using Newtonsoft.Json; public class 业务服务类测试 { private readonly Mock<ICognitoPoolService> _mockCognitoService; private readonly Mock<IRestClientFactory> _mockClientFactory; private readonly Mock<IRestClient> _mockRestClient; private readonly 你的业务服务类 _service; private const string TestBaseUrl = "https://test.api.com"; private const string TestEndpoint = "/api/user/get"; private const string ValidTestToken = "valid_token_123"; public 业务服务类测试() { _mockCognitoService = new Mock<ICognitoPoolService>(); _mockClientFactory = new Mock<IRestClientFactory>(); _mockRestClient = new Mock<IRestClient>(); // 配置工厂返回我们Mock的RestClient实例 _mockClientFactory.Setup(f => f.Create(It.Is<string>(url => url == TestBaseUrl))) .Returns(_mockRestClient.Object); // 注入所有Mock依赖到业务服务 // 这里的IConfiguration可以用Mock或者直接传测试配置,根据你的实际构造函数调整即可 var mockConfig = new Mock<IConfiguration>(); mockConfig.Setup(c => c["RestClient:BaseUrl"]).Returns(TestBaseUrl); mockConfig.Setup(c => c["RestClient:GetUserEndpoint"]).Returns(TestEndpoint); _service = new 你的业务服务类( _mockCognitoService.Object, mockConfig.Object, _mockClientFactory.Object); } [Fact] public async Task GetUser_请求成功时_返回正确用户信息() { // 准备测试数据 var testUsername = "zhangsan"; var expectedUser = new TaUser { Username = testUsername, UserId = 1001 }; var mockIdentityResponse = new IdentityProviderUser { Outcome = new Outcome { StatusCode = 200 }, User = expectedUser }; var mockRestResponse = new RestResponse { Content = JsonConvert.SerializeObject(mockIdentityResponse), StatusCode = System.Net.HttpStatusCode.OK }; // 配置Mock返回值 _mockCognitoService.Setup(s => s.GetToken()).ReturnsAsync(ValidTestToken); _mockRestClient.Setup(c => c.ExecutePostAsync(It.IsAny<RestRequest>())) .ReturnsAsync(mockRestResponse); // 执行测试 var result = await _service.GetUser(testUsername); // 断言结果 Assert.NotNull(result); Assert.Equal(expectedUser.Username, result.Username); Assert.Equal(expectedUser.UserId, result.UserId); // 可选:验证请求参数符合预期 _mockRestClient.Verify(c => c.ExecutePostAsync(It.Is<RestRequest>(req => // 验证Authorization头正确 req.Parameters.Any(p => p.Name == "Authorization" && p.Value.ToString() == $"Bearer {ValidTestToken}") // 验证请求体正确 && JsonConvert.DeserializeObject<GetTaUserRequest>(req.Parameters.First(p => p.Type == ParameterType.RequestBody).Value.ToString()).Username == testUsername )), Times.Once); } [Fact] public async Task GetUser_请求失败时_抛出对应异常() { // 准备测试数据 var testUsername = "nonexist_user"; var errorMessage = "用户不存在"; var mockIdentityResponse = new IdentityProviderUser { Outcome = new Outcome { StatusCode = 404, Message = errorMessage }, User = null }; var mockRestResponse = new RestResponse { Content = JsonConvert.SerializeObject(mockIdentityResponse), StatusCode = System.Net.HttpStatusCode.NotFound }; // 配置Mock返回值 _mockCognitoService.Setup(s => s.GetToken()).ReturnsAsync(ValidTestToken); _mockRestClient.Setup(c => c.ExecutePostAsync(It.IsAny<RestRequest>())) .ReturnsAsync(mockRestResponse); // 执行并断言异常 var exception = await Assert.ThrowsAsync<Exception>(() => _service.GetUser(testUsername)); Assert.Equal($"{errorMessage} ", exception.Message); } }
简化方案
如果你的所有RestClient请求都用同一个固定BaseUrl,不需要动态创建不同地址的实例,可以不用工厂,直接把IRestClient作为依赖注入到业务类即可,测试时直接MockIRestClient的行为,改造成本更低。
内容的提问来源于stack exchange,提问作者Murphie
相关产品推荐
相关产品推荐

