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

使用Moq与RestSharp进行Xunit测试:模拟分页API响应

使用Xunit + Moq + RestSharp 测试TopLevelCustomersClient

测试思路

我们需要Mock IRestClientFactory 和 IRestClient,模拟API的分页响应,验证GetAllCustomers方法能正确收集所有客户ID,且确认响应状态为成功(OK)。

首先补全原代码中缺失的依赖类型:

// 原代码中用到的响应实体类
public class CustomerResponse
{
    public List<string> TopLevelCustomerIds { get; set; }
    public string Next { get; set; }
}

// 原代码中的接口定义
public interface ITopLevelCustomersClient
{
    Task<List<string>> GetAllCustomers();
}

测试代码实现

1. 安装必要NuGet包

测试项目需安装以下包:

  • xunit
  • xunit.runner.visualstudio
  • Moq
  • RestSharp
  • Newtonsoft.Json

2. 编写测试类

using Moq;
using RestSharp;
using System.Net;
using Newtonsoft.Json;
using Xunit;
using Microsoft.Extensions.Logging;

namespace test.Tests
{
    public class TopLevelCustomersClientTests
    {
        private readonly Mock<IRestClientFactory> _restClientFactoryMock;
        private readonly Mock<IRestClient> _restClientMock;
        private readonly Mock<ILogger<TopLevelCustomersClient>> _loggerMock;
        private readonly TopLevelCustomersClient _client;

        public TopLevelCustomersClientTests()
        {
            // 初始化Mock对象
            _restClientMock = new Mock<IRestClient>();
            _restClientFactoryMock = new Mock<IRestClientFactory>();
            _loggerMock = new Mock<ILogger<TopLevelCustomersClient>>();

            // 配置工厂返回Mock的RestClient
            _restClientFactoryMock.Setup(f => f.CreateClient(It.IsAny<string>()))
                .Returns(_restClientMock.Object);

            // 初始化待测试客户端
            _client = new TopLevelCustomersClient("https://api.example.com/", _loggerMock.Object, _restClientFactoryMock.Object);
        }

        [Fact]
        public async Task GetAllCustomers_ShouldReturnAllIds_SinglePageResponse()
        {
            // Arrange
            var expectedIds = new List<string> { "CUST-001", "CUST-002" };
            var responseContent = JsonConvert.SerializeObject(new CustomerResponse
            {
                TopLevelCustomerIds = expectedIds,
                Next = null
            });

            var mockResponse = new RestResponse
            {
                StatusCode = HttpStatusCode.OK,
                IsSuccessful = true,
                Content = responseContent
            };

            _restClientMock.Setup(c => c.GetAsync(It.IsAny<RestRequest>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(mockResponse);

            // Act
            var result = await _client.GetAllCustomers();

            // Assert
            Assert.Equal(expectedIds.Count, result.Count);
            Assert.Contains("CUST-001", result);
            Assert.Contains("CUST-002", result);
            _restClientMock.Verify(c => c.GetAsync(It.IsAny<RestRequest>(), It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task GetAllCustomers_ShouldCombineIds_MultiplePages()
        {
            // Arrange
            var page1Ids = new List<string> { "CUST-001", "CUST-002" };
            var page2Ids = new List<string> { "CUST-003", "CUST-004" };
            var combinedIds = page1Ids.Concat(page2Ids).ToList();

            var page1Response = new RestResponse
            {
                StatusCode = HttpStatusCode.OK,
                IsSuccessful = true,
                Content = JsonConvert.SerializeObject(new CustomerResponse
                {
                    TopLevelCustomerIds = page1Ids,
                    Next = "https://api.example.com/customers?page=2"
                })
            };

            var page2Response = new RestResponse
            {
                StatusCode = HttpStatusCode.OK,
                IsSuccessful = true,
                Content = JsonConvert.SerializeObject(new CustomerResponse
                {
                    TopLevelCustomerIds = page2Ids,
                    Next = null
                })
            };

            _restClientMock.SetupSequence(c => c.GetAsync(It.IsAny<RestRequest>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(page1Response)
                .ReturnsAsync(page2Response);

            // Act
            var result = await _client.GetAllCustomers();

            // Assert
            Assert.Equal(combinedIds.Count, result.Count);
            foreach (var id in combinedIds)
            {
                Assert.Contains(id, result);
            }
            _restClientMock.Verify(c => c.GetAsync(It.IsAny<RestRequest>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
        }

        [Fact]
        public async Task GetAllCustomers_ShouldThrowException_ResponseFailed()
        {
            // Arrange
            var mockResponse = new RestResponse
            {
                StatusCode = HttpStatusCode.BadRequest,
                IsSuccessful = false,
                StatusDescription = "Bad Request"
            };

            _restClientMock.Setup(c => c.GetAsync(It.IsAny<RestRequest>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(mockResponse);

            // Act & Assert
            var exception = await Assert.ThrowsAsync<Exception>(() => _client.GetAllCustomers());
            Assert.Contains("Error getting customers: Bad Request", exception.Message);
        }
    }
}

关键配置说明

  • Mock IRestClientFactory:让它返回预先配置的IRestClient实例,避免调用真实API。
  • 模拟成功响应:设置IsSuccessful = true和StatusCode = HttpStatusCode.OK,同时序列化CustomerResponse作为响应内容,模拟API返回数据。
  • 处理分页逻辑:用SetupSequence配置GetAsync多次调用返回不同分页响应,模拟多页数据场景。
  • 验证核心逻辑:断言返回的客户ID集合符合预期,同时验证GetAsync调用次数与分页数量匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 05:55:08