如何使用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
相关产品推荐
相关产品推荐

