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

