如何用MOQ模拟OpenAIClient以返回Completions响应?
解决Moq模拟OpenAIClient返回Completions响应的问题
核心问题
Completions类及其内部成员的构造函数标记为internal,无法直接通过反序列化或手动实例化创建对象,导致Mock设置失败。
解决方案
方案1:通过反射创建Completions实例
利用反射绕过internal限制,手动从JSON提取值填充属性:
private async Task<Response<Completions>> CreateMockCompletionsResponse(string completionsJson) { // 解析JSON到动态对象 dynamic jsonObj = JsonConvert.DeserializeObject(completionsJson); // 反射创建Completions实例 var completionsType = typeof(Completions); var completions = (Completions)Activator.CreateInstance(completionsType, true); // 设置顶层属性 completionsType.GetProperty("Id")?.SetValue(completions, jsonObj.id); completionsType.GetProperty("Created")?.SetValue(completions, DateTimeOffset.FromUnixTimeSeconds(jsonObj.created)); // 处理Choices集合 var choicesType = typeof(CompletionChoice); var choicesList = new List<CompletionChoice>(); foreach (var choiceJson in jsonObj.choices) { var choice = (CompletionChoice)Activator.CreateInstance(choicesType, true); choicesType.GetProperty("Text")?.SetValue(choice, choiceJson.text); choicesType.GetProperty("Index")?.SetValue(choice, choiceJson.index); choicesType.GetProperty("FinishReason")?.SetValue(choice, choiceJson.finish_reason); choicesList.Add(choice); } completionsType.GetProperty("Choices")?.SetValue(completions, choicesList); // 构建并返回Response var mockResponse = Mock.Of<Response>(); return Response.FromValue(completions, mockResponse); } // 应用到Mock设置 client.Setup(x => x.GetCompletionsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) .ReturnsAsync(CreateMockCompletionsResponse(completionsJson));
方案2:直接Mock Completions及其内部对象
无需实例化真实类,用Moq模拟必要属性:
private async Task<Response<Completions>> CreateMockCompletionsResponse() { // Mock CompletionChoice var mockChoice = new Mock<CompletionChoice>(); mockChoice.Setup(c => c.Text).Returns("模拟的补全文本内容"); mockChoice.Setup(c => c.Index).Returns(0); mockChoice.Setup(c => c.FinishReason).Returns("stop"); // Mock Completions var mockCompletions = new Mock<Completions>(); mockCompletions.Setup(c => c.Id).Returns("mock-completion-id-123"); mockCompletions.Setup(c => c.Created).Returns(DateTimeOffset.Now); mockCompletions.Setup(c => c.Choices).Returns(new List<CompletionChoice> { mockChoice.Object }); // 构建Response var mockResponse = Mock.Of<Response>(); return Response.FromValue(mockCompletions.Object, mockResponse); } // 应用到Mock设置 client.Setup(x => x.GetCompletionsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) .ReturnsAsync(CreateMockCompletionsResponse());
方案3:DTO映射+反射复制(适合复杂JSON)
先反序列化为自定义DTO,再通过反射将值复制到Completions实例:
- 定义匹配结构的DTO:
public class CompletionsDto { public string Id { get; set; } public long Created { get; set; } public List<CompletionChoiceDto> Choices { get; set; } } public class CompletionChoiceDto { public string Text { get; set; } public int Index { get; set; } public string FinishReason { get; set; } }
- 映射并创建实例:
private async Task<Response<Completions>> CreateMockCompletionsResponse(string completionsJson) { var dto = JsonConvert.DeserializeObject<CompletionsDto>(completionsJson); var completions = (Completions)Activator.CreateInstance(typeof(Completions), true); // 复制顶层属性 typeof(CompletionsDto).GetProperties().ToList().ForEach(dtoProp => { var targetProp = typeof(Completions).GetProperty(dtoProp.Name); if (targetProp == null) return; if (dtoProp.Name == "Created") { targetProp.SetValue(completions, DateTimeOffset.FromUnixTimeSeconds((long)dtoProp.GetValue(dto))); } else if (dtoProp.Name == "Choices" && dto.Choices != null) { var choices = dto.Choices.Select(dtoChoice => { var choice = (CompletionChoice)Activator.CreateInstance(typeof(CompletionChoice), true); typeof(CompletionChoiceDto).GetProperties().ToList().ForEach(cDtoProp => { var cTargetProp = typeof(CompletionChoice).GetProperty(cDtoProp.Name); cTargetProp?.SetValue(choice, cDtoProp.GetValue(dtoChoice)); }); return choice; }).ToList(); targetProp.SetValue(completions, choices); } else { targetProp.SetValue(completions, dtoProp.GetValue(dto)); } }); var mockResponse = Mock.Of<Response>(); return Response.FromValue(completions, mockResponse); }
注意事项
- 反射操作需匹配目标类的属性名称和类型,API版本更新后需同步调整。
- Mock属性时只需设置测试场景用到的字段,无需覆盖全部属性。
- 反射创建的实例可能无法完全模拟真实响应的内部逻辑,需根据测试需求调整。
内容的提问来源于stack exchange,提问作者Punter Vicky
相关产品推荐
相关产品推荐

