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

如何对Hot Chocolate的ObjectFieldDescriptorAttribute进行集成与单元测试

如何对Hot Chocolate中的ObjectFieldDescriptorAttribute实现进行测试?

一、集成测试

集成测试会模拟完整的GraphQL执行流程,验证ValidApiKeyAttribute在真实请求中的行为。我们需要构建测试用的服务集合,注入必要的依赖,并模拟HttpContext和配置项。

示例代码

[Fact]
public async Task Query_GetSomething_WithValidApiKey_Succeeds()
{
    // 准备测试配置
    var authSettings = new AuthenticationSettings
    {
        ValidApiKey = "test-valid-key"
    };

    // 构建服务集合
    var services = new ServiceCollection()
        // 注册GraphQL相关服务
        .AddGraphQL()
        .AddQueryType<Query>()
        // 注入配置项
        .AddSingleton(Options.Create(authSettings))
        // 注入HttpContextAccessor并模拟HttpContext
        .AddSingleton<IHttpContextAccessor>(sp =>
        {
            var httpContext = new DefaultHttpContext();
            // 在Header中添加合法的ApiKey
            httpContext.Request.Headers.Add("X-Api-Key", authSettings.ValidApiKey);
            return new HttpContextAccessor { HttpContext = httpContext };
        })
        .Services;

    // 构建请求执行器
    var executor = await services.BuildRequestExecutorAsync();

    // 构建GraphQL查询请求
    var request = QueryRequestBuilder.New()
        .SetQuery("{ getSomething { id name } }") // 替换为实际的字段结构
        .Create();

    // 执行请求
    var result = await executor.ExecuteAsync(request);

    // 断言:请求成功,无错误
    Assert.Null(result.Errors);
    Assert.NotNull(result.Data);
}

[Fact]
public async Task Query_GetSomething_WithInvalidApiKey_Fails()
{
    // 准备测试配置
    var authSettings = new AuthenticationSettings
    {
        ValidApiKey = "test-valid-key"
    };

    var services = new ServiceCollection()
        .AddGraphQL()
        .AddQueryType<Query>()
        .AddSingleton(Options.Create(authSettings))
        .AddSingleton<IHttpContextAccessor>(sp =>
        {
            var httpContext = new DefaultHttpContext();
            // 添加非法的ApiKey
            httpContext.Request.Headers.Add("X-Api-Key", "invalid-key");
            return new HttpContextAccessor { HttpContext = httpContext };
        })
        .Services;

    var executor = await services.BuildRequestExecutorAsync();

    var request = QueryRequestBuilder.New()
        .SetQuery("{ getSomething { id name } }")
        .Create();

    var result = await executor.ExecuteAsync(request);

    // 断言:返回错误信息
    Assert.NotNull(result.Errors);
    Assert.NotEmpty(result.Errors);
}

关键说明

  • 通过IHttpContextAccessor模拟带有请求头的HttpContext,还原真实的ApiKey传递场景。
  • 注入IOptions<AuthenticationSettings>提供验证所需的合法密钥配置。
  • 分别测试合法和非法ApiKey的场景,验证Attribute的逻辑是否生效。

二、单元测试

单元测试分为两部分:测试ValidApiKeyAttribute的配置逻辑,以及单独测试中间件的业务逻辑。

1. 测试Attribute的OnConfigure方法

验证Attribute是否正确将中间件添加到字段描述符上:

[Fact]
public void ValidApiKeyAttribute_OnConfigure_AddsMiddleware()
{
    // Mock依赖
    var descriptorContext = Mock.Of<IDescriptorContext>();
    var memberInfo = typeof(Query).GetMethod(nameof(Query.GetSomething));
    var fieldDescriptor = new Mock<IObjectFieldDescriptor>();

    // 跟踪是否调用了Use方法
    bool middlewareAdded = false;
    fieldDescriptor.Setup(d => d.Use(It.IsAny<FieldMiddleware>()))
        .Callback<FieldMiddleware>(_ => middlewareAdded = true)
        .Returns(fieldDescriptor.Object);

    // 实例化Attribute并调用配置方法
    var attribute = new ValidApiKeyAttribute();
    attribute.OnConfigure(descriptorContext, fieldDescriptor.Object, memberInfo);

    // 断言:中间件已被添加
    Assert.True(middlewareAdded);
}

2. 单独测试中间件逻辑

直接测试中间件的委托,不需要依赖Attribute或GraphQL字段配置:

[Fact]
public async Task ValidApiKeyMiddleware_WithValidKey_ProceedsToNext()
{
    // 准备测试依赖
    var authSettings = new AuthenticationSettings { ValidApiKey = "test-key" };
    var httpContext = new DefaultHttpContext();
    httpContext.Request.Headers.Add("X-Api-Key", authSettings.ValidApiKey);

    // 构建服务提供者
    var services = new ServiceCollection()
        .AddSingleton(Options.Create(authSettings))
        .AddSingleton<IHttpContextAccessor>(new HttpContextAccessor { HttpContext = httpContext })
        .BuildServiceProvider();

    // 模拟FieldContext
    var fieldContext = new Mock<IMiddlewareContext>();
    fieldContext.Setup(c => c.Services).Returns(services);

    // 标记下一个中间件是否被调用
    bool nextCalled = false;
    FieldDelegate next = async ctx =>
    {
        nextCalled = true;
        await Task.CompletedTask;
    };

    // 定义中间件逻辑
    FieldMiddleware middleware = next => async context =>
    {
        var httpContextAccessor = context.Services.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
        var authenticationSettings = context.Services.GetService(typeof(IOptions<AuthenticationSettings>)) as IOptions<AuthenticationSettings>;

        var apiKey = httpContextAccessor?.HttpContext?.Request.Headers["X-Api-Key"].FirstOrDefault();
        if (apiKey != authenticationSettings.Value.ValidApiKey)
        {
            context.ReportError(new Error("Invalid API Key"));
            return;
        }

        await next(context);
    };

    // 执行中间件
    await middleware(next)(fieldContext.Object);

    // 断言:下一个中间件被调用,且无错误
    Assert.True(nextCalled);
    fieldContext.Verify(c => c.ReportError(It.IsAny<Error>()), Times.Never);
}

[Fact]
public async Task ValidApiKeyMiddleware_WithInvalidKey_ReportsError()
{
    var authSettings = new AuthenticationSettings { ValidApiKey = "test-key" };
    var httpContext = new DefaultHttpContext();
    httpContext.Request.Headers.Add("X-Api-Key", "invalid-key");

    var services = new ServiceCollection()
        .AddSingleton(Options.Create(authSettings))
        .AddSingleton<IHttpContextAccessor>(new HttpContextAccessor { HttpContext = httpContext })
        .BuildServiceProvider();

    var fieldContext = new Mock<IMiddlewareContext>();
    fieldContext.Setup(c => c.Services).Returns(services);

    bool nextCalled = false;
    FieldDelegate next = async ctx =>
    {
        nextCalled = true;
        await Task.CompletedTask;
    };

    var middleware = next => async context =>
    {
        var httpContextAccessor = context.Services.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
        var authenticationSettings = context.Services.GetService(typeof(IOptions<AuthenticationSettings>)) as IOptions<AuthenticationSettings>;

        var apiKey = httpContextAccessor?.HttpContext?.Request.Headers["X-Api-Key"].FirstOrDefault();
        if (apiKey != authenticationSettings.Value.ValidApiKey)
        {
            context.ReportError(new Error("Invalid API Key"));
            return;
        }

        await next(context);
    };

    await middleware(next)(fieldContext.Object);

    // 断言:下一个中间件未被调用,且报告了错误
    Assert.False(nextCalled);
    fieldContext.Verify(c => c.ReportError(It.Is<Error>(e => e.Message == "Invalid API Key")), Times.Once);
}

关键说明

  • 测试Attribute时,只需验证它是否正确调用了descriptor.Use()方法,中间件的具体逻辑交给单独的单元测试。
  • 单独测试中间件时,模拟IMiddlewareContext、服务提供者和下一个委托,验证不同输入下的行为是否符合预期。
  • 建议将中间件逻辑从Attribute中单独封装,便于单元测试和代码复用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 19:40:29