如何使用Moq模拟Microsoft Graph SDK v5的GraphServiceClient进行单元测试?
使用Moq为Graph API相关方法搭建单元测试环境
核心思路
你要测试的方法依赖于Microsoft Graph SDK的客户端,而Graph SDK本身提供了全套抽象接口(无需自行定义),可以直接用Moq对这些接口进行Mock,模拟Graph API的返回结果,从而隔离外部依赖,专注测试业务逻辑。
步骤与代码示例
假设你的业务类名为WebinarService,构造函数注入IGraphServiceClient:
public class WebinarService { private readonly IGraphServiceClient _client; public WebinarService(IGraphServiceClient client) { _client = client; } // 待测试的方法 public async Task<List<VirtualEventWebinar>> GetMany(string title) { var result = await _client.Solutions.VirtualEvents.Webinars.GetAsync(); return result?.Value != null ? result.Value.Where(x => x.DisplayName.Contains(title)).ToList() : []; } }
测试用例编写
以下是覆盖全场景的单元测试代码(以xunit为例):
using Moq; using Microsoft.Graph; using Xunit; public class WebinarServiceTests { [Fact] public async Task GetMany_TitleMatches_ReturnsFilteredList() { // 准备测试数据 var testWebinars = new List<VirtualEventWebinar> { new() { DisplayName = "Azure Webinar" }, new() { DisplayName = "Office 365 Workshop" }, new() { DisplayName = "Azure Developer Session" } }; // Mock 接口响应 var mockResponse = new Mock<IVirtualEventsWebinarsCollectionResponse>(); mockResponse.Setup(r => r.Value).Returns(testWebinars); // Mock Webinars请求构建器的GetAsync方法 var mockWebinarsBuilder = new Mock<IVirtualEventsWebinarsCollectionRequestBuilder>(); mockWebinarsBuilder.Setup(b => b.GetAsync(It.IsAny<CancellationToken>())) .ReturnsAsync(mockResponse.Object); // 逐层Mock上层依赖 var mockVirtualEventsBuilder = new Mock<IVirtualEventsRequestBuilder>(); mockVirtualEventsBuilder.Setup(b => b.Webinars).Returns(mockWebinarsBuilder.Object); var mockSolutionsBuilder = new Mock<ISolutionsRequestBuilder>(); mockSolutionsBuilder.Setup(b => b.VirtualEvents).Returns(mockVirtualEventsBuilder.Object); var mockGraphClient = new Mock<IGraphServiceClient>(); mockGraphClient.Setup(c => c.Solutions).Returns(mockSolutionsBuilder.Object); // 初始化被测服务 var service = new WebinarService(mockGraphClient.Object); // 执行测试 var result = await service.GetMany("Azure"); // 断言结果 Assert.Equal(2, result.Count); Assert.All(result, w => w.DisplayName.Contains("Azure")); } [Fact] public async Task GetMany_ResultIsNull_ReturnsEmptyList() { var mockWebinarsBuilder = new Mock<IVirtualEventsWebinarsCollectionRequestBuilder>(); mockWebinarsBuilder.Setup(b => b.GetAsync(It.IsAny<CancellationToken>())) .ReturnsAsync((IVirtualEventsWebinarsCollectionResponse)null); // 组装上层Mock var mockVirtualEventsBuilder = new Mock<IVirtualEventsRequestBuilder>(); mockVirtualEventsBuilder.Setup(b => b.Webinars).Returns(mockWebinarsBuilder.Object); var mockSolutionsBuilder = new Mock<ISolutionsRequestBuilder>(); mockSolutionsBuilder.Setup(b => b.VirtualEvents).Returns(mockVirtualEventsBuilder.Object); var mockGraphClient = new Mock<IGraphServiceClient>(); mockGraphClient.Setup(c => c.Solutions).Returns(mockSolutionsBuilder.Object); var service = new WebinarService(mockGraphClient.Object); var result = await service.GetMany("Any Title"); Assert.Empty(result); } [Fact] public async Task GetMany_ResultValueIsNull_ReturnsEmptyList() { var mockResponse = new Mock<IVirtualEventsWebinarsCollectionResponse>(); mockResponse.Setup(r => r.Value).Returns((List<VirtualEventWebinar>)null); var mockWebinarsBuilder = new Mock<IVirtualEventsWebinarsCollectionRequestBuilder>(); mockWebinarsBuilder.Setup(b => b.GetAsync(It.IsAny<CancellationToken>())) .ReturnsAsync(mockResponse.Object); // 组装上层Mock var mockVirtualEventsBuilder = new Mock<IVirtualEventsRequestBuilder>(); mockVirtualEventsBuilder.Setup(b => b.Webinars).Returns(mockWebinarsBuilder.Object); var mockSolutionsBuilder = new Mock<ISolutionsRequestBuilder>(); mockSolutionsBuilder.Setup(b => b.VirtualEvents).Returns(mockVirtualEventsBuilder.Object); var mockGraphClient = new Mock<IGraphServiceClient>(); mockGraphClient.Setup(c => c.Solutions).Returns(mockSolutionsBuilder.Object); var service = new WebinarService(mockGraphClient.Object); var result = await service.GetMany("Any Title"); Assert.Empty(result); } [Fact] public async Task GetMany_NoMatchingTitles_ReturnsEmptyList() { var testWebinars = new List<VirtualEventWebinar> { new() { DisplayName = "Azure Webinar" }, new() { DisplayName = "Office 365 Workshop" } }; var mockResponse = new Mock<IVirtualEventsWebinarsCollectionResponse>(); mockResponse.Setup(r => r.Value).Returns(testWebinars); var mockWebinarsBuilder = new Mock<IVirtualEventsWebinarsCollectionRequestBuilder>(); mockWebinarsBuilder.Setup(b => b.GetAsync(It.IsAny<CancellationToken>())) .ReturnsAsync(mockResponse.Object); // 组装上层Mock var mockVirtualEventsBuilder = new Mock<IVirtualEventsRequestBuilder>(); mockVirtualEventsBuilder.Setup(b => b.Webinars).Returns(mockWebinarsBuilder.Object); var mockSolutionsBuilder = new Mock<ISolutionsRequestBuilder>(); mockSolutionsBuilder.Setup(b => b.VirtualEvents).Returns(mockVirtualEventsBuilder.Object); var mockGraphClient = new Mock<IGraphServiceClient>(); mockGraphClient.Setup(c => c.Solutions).Returns(mockSolutionsBuilder.Object); var service = new WebinarService(mockGraphClient.Object); var result = await service.GetMany("AWS"); Assert.Empty(result); } }
关键说明
- 无需自定义接口:Microsoft Graph SDK提供了
IGraphServiceClient、ISolutionsRequestBuilder、IVirtualEventsRequestBuilder、IVirtualEventsWebinarsCollectionRequestBuilder等一系列抽象接口,直接用于Mock即可。 - 分层Mock:从最底层的响应结果开始Mock,逐层向上构建依赖关系,最终得到可注入的
IGraphServiceClientMock实例。 - 覆盖全分支:测试用例覆盖了标题匹配、无匹配、响应为null、响应Value为null四种核心场景,确保业务逻辑的正确性。
内容的提问来源于stack exchange,提问作者ShaneKm
相关产品推荐
相关产品推荐

