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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 20:15:01