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

如何用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实例:

  1. 定义匹配结构的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; }
}
  1. 映射并创建实例:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 00:22:23