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

如何使用Mock将患者服务类方法改造为可测试的Xunit用例

问题描述

我有一个PatientService类包含两个方法,代码如下:

public class PatientService : IPatientService { 
    private readonly IRestClient _restClient; 
    private readonly IAppSettings _appSettings; 
    public PatientService(IRestClient restClient, IAppSettings appSettings) { 
        _restClient = restClient; 
        _appSettings = appSettings; 
    } 
    public async Task<IList<PatientViewModel>> GetPatients(int wardId) { 
        var url = _appSettings.Server + _appSettings.PatientServiceEndPoint + wardId; 
        var token = _appSettings.Token; 
        return GetPatientList(await _restClient.GetAsync<List<PatientInfo>>(url, token)); 
    } 
    public IList<PatientViewModel> GetPatientList(IList<PatientInfo> patientInfoList) { 
        return patientInfoList.Select(p => new PatientViewModel(p)).ToList(); 
    } 
}

我想给这个类编写Xunit测试,但目前写的测试代码不知道怎么继续:

private readonly PatientListPageViewModel _patientListPageViewModel; 
private readonly Mock<IPatientService> _patient; 
public PatientServiceTests() { 
    _patient = new Mock<IPatientService>(); 
    _patientListPageViewModel = new PatientListPageViewModel(_patient.Object); 
} 
[Fact] 
public void GetListByWard_PassingWardId_GetPatientsCountAccordingToWardId() { 
}

请问如何改造服务类使其可测试,并完成对应的Xunit测试用例?


解决方案

首先,你的PatientService其实已经具备良好的可测试性了——它通过构造函数注入了IRestClient和IAppSettings这两个依赖,我们不需要改造服务类,只需要调整测试类的思路:我们要测试的是PatientService本身,所以应该mock它的依赖(IRestClient和IAppSettings),而不是mockIPatientService。

1. 调整测试类的依赖注入

重新编写测试类的构造函数,注入我们需要mock的依赖:

using Moq;
using Xunit;
using System.Collections.Generic;
using System.Threading.Tasks;

public class PatientServiceTests
{
    private readonly Mock<IRestClient> _mockRestClient;
    private readonly Mock<IAppSettings> _mockAppSettings;
    private readonly PatientService _patientService;

    public PatientServiceTests()
    {
        // 初始化mock对象
        _mockRestClient = new Mock<IRestClient>();
        _mockAppSettings = new Mock<IAppSettings>();
        // 创建要测试的PatientService实例,传入mock的依赖
        _patientService = new PatientService(_mockRestClient.Object, _mockAppSettings.Object);
    }
}

2. 编写GetPatientList方法的测试用例

这个方法是同步的,负责把PatientInfo列表转换成PatientViewModel列表,我们可以直接测试它的转换逻辑:

[Fact]
public void GetPatientList_ValidPatientInfoList_ReturnsMatchingViewModelList()
{
    // Arrange:准备测试数据
    var testPatientInfos = new List<PatientInfo>
    {
        new PatientInfo { Id = 1, Name = "张三", WardId = 101 },
        new PatientInfo { Id = 2, Name = "李四", WardId = 101 }
    };

    // Act:调用要测试的方法
    var result = _patientService.GetPatientList(testPatientInfos);

    // Assert:验证结果是否符合预期
    Assert.NotNull(result);
    Assert.Equal(testPatientInfos.Count, result.Count);
    Assert.Equal(testPatientInfos[0].Id, result[0].Id);
    Assert.Equal(testPatientInfos[0].Name, result[0].Name);
}

3. 编写GetPatients方法的异步测试用例

这个方法是异步的,依赖IRestClient的GetAsync方法和IAppSettings的配置,我们需要mock这些依赖的行为:

[Fact]
public async Task GetPatients_ValidWardId_ReturnsCorrectPatientViewModels()
{
    // Arrange:准备测试数据和mock行为
    var wardId = 101;
    var mockServerUrl = "https://api.example.com/";
    var mockEndPoint = "patients/ward/";
    var mockToken = "test-token-123";
    var expectedUrl = $"{mockServerUrl}{mockEndPoint}{wardId}";

    // 设置IAppSettings的mock返回值
    _mockAppSettings.Setup(x => x.Server).Returns(mockServerUrl);
    _mockAppSettings.Setup(x => x.PatientServiceEndPoint).Returns(mockEndPoint);
    _mockAppSettings.Setup(x => x.Token).Returns(mockToken);

    // 设置IRestClient的GetAsync方法返回模拟的PatientInfo列表
    var mockPatientInfos = new List<PatientInfo>
    {
        new PatientInfo { Id = 1, Name = "张三", WardId = wardId },
        new PatientInfo { Id = 2, Name = "李四", WardId = wardId }
    };
    _mockRestClient.Setup(x => x.GetAsync<List<PatientInfo>>(expectedUrl, mockToken))
                   .ReturnsAsync(mockPatientInfos);

    // Act:调用异步方法
    var result = await _patientService.GetPatients(wardId);

    // Assert:验证结果和mock的调用情况
    Assert.NotNull(result);
    Assert.Equal(mockPatientInfos.Count, result.Count);
    // 验证IRestClient的GetAsync方法是否被调用了一次,且参数正确
    _mockRestClient.Verify(x => x.GetAsync<List<PatientInfo>>(expectedUrl, mockToken), Times.Once);
}

额外优化建议(可选)

如果GetPatientList方法只在PatientService内部被调用,你可以把它改成私有方法,这样可以减少对外暴露的接口。如果改成私有方法,我们不需要单独测试它——因为测试GetPatients方法的时候会间接覆盖它的逻辑。修改后的服务类代码:

public class PatientService : IPatientService { 
    private readonly IRestClient _restClient; 
    private readonly IAppSettings _appSettings; 
    public PatientService(IRestClient restClient, IAppSettings appSettings) { 
        _restClient = restClient; 
        _appSettings = appSettings; 
    } 
    public async Task<IList<PatientViewModel>> GetPatients(int wardId) { 
        var url = _appSettings.Server + _appSettings.PatientServiceEndPoint + wardId; 
        var token = _appSettings.Token; 
        return GetPatientList(await _restClient.GetAsync<List<PatientInfo>>(url, token)); 
    } 
    // 改成私有方法
    private IList<PatientViewModel> GetPatientList(IList<PatientInfo> patientInfoList) { 
        return patientInfoList.Select(p => new PatientViewModel(p)).ToList(); 
    } 
}

这种情况下,只需要保留GetPatients的测试用例即可,因为它已经覆盖了私有方法的转换逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:37:00