在 ASP.NET Core 中测试 gRPC 服务
测试是构建稳定且可维护的软件的一个重要方面。 本文介绍如何测试 ASP.NET Core gRPC 服务。
有三种常见的测试 gRPC 服务的方法:
- 单元测试:直接从单元测试库测试 gRPC 服务。
- 集成测试:gRPC 应用托管在 TestServer(
Microsoft.AspNetCore.TestHost
包中的内存中测试服务器)中。 通过单元测试库中的 gRPC 客户端调用 gRPC 服务来对其进行测试。 - 手动测试:使用即席调用测试 gRPC 服务器。 有关如何在 gPRC 服务中使用命令行和 UI 工具的信息,请参阅在 ASP.NET Core 中使用 gRPCurl 和 gRPCui 测试 gRPC 服务。
在单元测试中,只涉及 gRPC 服务。 必须模拟注入到服务中的依赖项。 在集成测试中,gRPC 服务及其辅助基础结构是测试的一部分。 这包括应用启动、依赖项注入、路由和身份验证以及授权。
示例可测试服务
若要演示服务测试,请在示例应用中查看以下服务。
TesterService
使用 gRPC 的四种方法类型返回问候语。
public class TesterService : Tester.TesterBase
{
private readonly IGreeter _greeter;
public TesterService(IGreeter greeter)
{
_greeter = greeter;
}
public override Task<HelloReply> SayHelloUnary(HelloRequest request,
ServerCallContext context)
{
var message = _greeter.Greet(request.Name);
return Task.FromResult(new HelloReply { Message = message });
}
public override async Task SayHelloServerStreaming(HelloRequest request,
IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
var i = 0;
while (!context.CancellationToken.IsCancellationRequested)
{
var message = _greeter.Greet($"{request.Name} {++i}");
await responseStream.WriteAsync(new HelloReply { Message = message });
await Task.Delay(1000);
}
}
public override async Task<HelloReply> SayHelloClientStreaming(
IAsyncStreamReader<HelloRequest> requestStream, ServerCallContext context)
{
var names = new List<string>();
await foreach (var request in requestStream.ReadAllAsync())
{
names.Add(request.Name);
}
var message = _greeter.Greet(string.Join(", ", names));
return new HelloReply { Message = message };
}
public override async Task SayHelloBidirectionalStreaming(
IAsyncStreamReader<HelloRequest> requestStream,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
await foreach (var request in requestStream.ReadAllAsync())
{
await responseStream.WriteAsync(
new HelloReply { Message = _greeter.Greet(request.Name) });
}
}
}
前面的 gRPC 服务:
- 遵循显式依赖关系原则。
- 期望依赖关系注入 (DI) 提供
IGreeter
的实例。 - 可以通过使用 mock 对象框架(如 Moq)的模拟
IGreeter
服务进行测试。 模拟对象是由一组预先确定的用于测试的属性和方法行为的对象。 有关详细信息,请参阅 ASP.NET Core 中的集成测试。
单元测试 gRPC 服务
单元测试库可以通过调用其方法直接测试 gRPC 服务。 单元测试单独测试 gRPC 服务。
[Fact]
public async Task SayHelloUnaryTest()
{
// Arrange
var mockGreeter = new Mock<IGreeter>();
mockGreeter.Setup(
m => m.Greet(It.IsAny<string>())).Returns((string s) => $"Hello {s}");
var service = new TesterService(mockGreeter.Object);
// Act
var response = await service.SayHelloUnary(
new HelloRequest { Name = "Joe" }, TestServerCallContext.Create());
// Assert
mockGreeter.Verify(v => v.Greet("Joe"));
Assert.Equal("Hello Joe", response.Message);
}
前面的单元测试:
- 使用 Moq 模拟
IGreeter
。 - 使用请求消息和
ServerCallContext
来执行SayHelloUnary
方法。 所有服务方法均具有ServerCallContext
参数。 在此测试中,使用TestServerCallContext.Create()
帮助程序方法提供类型。 此帮助程序方法包含在示例代码中。 - 做出断言:
- 验证请求名称是否传递给
IGreeter
。 - 服务返回预期的答复消息。
- 验证请求名称是否传递给
gRPC 方法中的单元测试 HttpContext
gRPC 方法可使用 ServerCallContext.GetHttpContext
扩展方法访问请求的 HttpContext。 若要对使用 HttpContext
的方法进行单元测试,必须在测试设置中配置上下文。 如果未配置 HttpContext,则 GetHttpContext
返回 null
。
若要在测试设置期间配置 HttpContext
,请创建一个新实例并使用 __HttpContext
键将其添加到 ServerCallContext.UserState
集合中。
var httpContext = new DefaultHttpContext();
var serverCallContext = TestServerCallContext.Create();
serverCallContext.UserState["__HttpContext"] = httpContext;
使用此调用上下文执行服务方法以使用配置的 HttpContext
实例。
集成测试 gRPC 服务
与单元测试相比,集成测试可在更广泛的级别上评估应用的组件。 gRPC 应用托管在 TestServer(Microsoft.AspNetCore.TestHost
包中的内存中测试服务器)中。
单元测试库启动 gRPC 应用,然后使用 gRPC 客户端测试 gRPC 服务。
示例代码包含可实现集成测试的基础结构:
GrpcTestFixture<TStartup>
类配置 ASP.NET Core 主机,并在内存中测试服务器中启动 gRPC 应用。IntegrationTestBase
类是集成测试继承自的基类型。 它包含用于创建 gRPC 客户端以调用 gRPC 应用的固定例程状态和 API。
[Fact]
public async Task SayHelloUnaryTest()
{
// Arrange
var client = new Tester.TesterClient(Channel);
// Act
var response = await client.SayHelloUnaryAsync(new HelloRequest { Name = "Joe" });
// Assert
Assert.Equal("Hello Joe", response.Message);
}
前面的集成测试:
- 使用
IntegrationTestBase
提供的通道创建 gRPC 客户端。 此类型包含在示例代码中。 - 使用 gRPC 客户端调用
SayHelloUnary
方法。 - 断言服务返回预期的答复消息。
注入模拟依赖项
在固定例程上使用 ConfigureWebHost
来重写依赖项。 当外部依赖项在测试环境中不可用时,重写依赖项很有用。 例如,使用外部支付网关的应用不应在执行测试时调用外部依赖项。 请改用模拟网关进行测试。
public MockedGreeterServiceTests(GrpcTestFixture<Startup> fixture,
ITestOutputHelper outputHelper) : base(fixture, outputHelper)
{
var mockGreeter = new Mock<IGreeter>();
mockGreeter.Setup(
m => m.Greet(It.IsAny<string>())).Returns((string s) =>
{
if (string.IsNullOrEmpty(s))
{
throw new ArgumentException("Name not provided.");
}
return $"Test {s}";
});
Fixture.ConfigureWebHost(builder =>
{
builder.ConfigureServices(
services => services.AddSingleton(mockGreeter.Object));
});
}
[Fact]
public async Task SayHelloUnaryTest_MockGreeter_Success()
{
// Arrange
var client = new Tester.TesterClient(Channel);
// Act
var response = await client.SayHelloUnaryAsync(
new HelloRequest { Name = "Joe" });
// Assert
Assert.Equal("Test Joe", response.Message);
}
前面的集成测试:
- 在测试类的 (
MockedGreeterServiceTests
) 构造函数:- 使用 Moq 模拟
IGreeter
。 - 重写使用
ConfigureWebHost
通过依赖项注入注册的IGreeter
。
- 使用 Moq 模拟
- 使用 gRPC 客户端调用
SayHelloUnary
方法。 - 根据模拟
IGreeter
实例断言预期的答复消息。
其他资源
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈