.NET 6 Web API调用gRPC代码优先服务的单元测试及Mock服务注入方案咨询
我来给你梳理几个可行的方案,帮你搞定.NET 6里调用gRPC Code First服务的单元测试,尤其是Mock注入的问题。
方案一:封装gRPC调用为抽象服务(推荐)
这种方式遵循依赖倒置原则,把gRPC的具体调用逻辑封装到一个抽象接口后面,既降低了耦合度,也让单元测试变得非常简单。
步骤1:定义抽象服务接口
先把你要调用的gRPC方法抽象成一个接口,比如你的gRPC服务是ProductService,有GetProductById方法,那可以这么定义:
public interface IProductGrpcService { Task<GetProductResponse> GetProductByIdAsync( GetProductRequest request, CancellationToken cancellationToken = default); // 其他gRPC方法也可以加到这里 }
步骤2:实现抽象接口
写一个具体的实现类,内部调用真实的gRPC客户端:
public class ProductGrpcService : IProductGrpcService { private readonly ProductService.ProductServiceClient _grpcClient; // 通过构造函数注入gRPC客户端 public ProductGrpcService(ProductService.ProductServiceClient grpcClient) { _grpcClient = grpcClient; } public async Task<GetProductResponse> GetProductByIdAsync( GetProductRequest request, CancellationToken cancellationToken = default) { // 调用真实的gRPC服务 return await _grpcClient.GetProductByIdAsync( request, cancellationToken: cancellationToken); } }
步骤3:在Program.cs中注册服务
把gRPC客户端和你的封装服务都注册到DI容器里:
var builder = WebApplication.CreateBuilder(args); // 注册gRPC客户端,配置后端服务地址 builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(options => { options.Address = new Uri(builder.Configuration["GrpcServices:ProductService"]); }); // 注册封装的gRPC服务接口 builder.Services.AddScoped<IProductGrpcService, ProductGrpcService>(); // 其他Web API配置... var app = builder.Build(); // ...
步骤4:编写单元测试
现在你的Web API控制器/业务类只需要依赖IProductGrpcService,单元测试时用Moq(或者其他Mock框架)模拟这个接口即可:
using Moq; using Xunit; public class ProductControllerTests { private readonly Mock<IProductGrpcService> _mockGrpcService; private readonly ProductController _controller; public ProductControllerTests() { // 初始化Mock对象 _mockGrpcService = new Mock<IProductGrpcService>(); // 注入Mock到控制器 _controller = new ProductController(_mockGrpcService.Object); } [Fact] public async Task GetProduct_ValidId_ReturnsOkResult() { // Arrange:准备测试数据和Mock行为 var testRequest = new GetProductRequest { ProductId = 1 }; var mockResponse = new GetProductResponse { ProductId = 1, Name = "Test Product", Price = 99.99M }; _mockGrpcService.Setup(s => s.GetProductByIdAsync( testRequest, It.IsAny<CancellationToken>())) .ReturnsAsync(mockResponse); // Act:执行测试方法 var result = await _controller.GetProduct(1); // Assert:验证结果和Mock调用 var okResult = Assert.IsType<OkObjectResult>(result); var productDto = Assert.IsType<ProductDto>(okResult.Value); Assert.Equal("Test Product", productDto.Name); // 确保Mock方法被调用了一次 _mockGrpcService.Verify(s => s.GetProductByIdAsync( testRequest, It.IsAny<CancellationToken>()), Times.Once); } }
方案二:直接Mock gRPC客户端
如果你不想额外封装接口,也可以直接Mock gRPC生成的客户端。不过要注意,gRPC客户端的方法返回的是AsyncUnaryCall<T>类型,需要手动构造这个对象。
步骤1:编写AsyncUnaryCall辅助方法
先写一个工具方法来创建模拟的AsyncUnaryCall实例:
public static class GrpcMockHelpers { public static AsyncUnaryCall<T> CreateMockUnaryCall<T>(T response) { var task = Task.FromResult(response); var headers = new Metadata(); var status = new Status(StatusCode.OK, "Success"); var trailers = new Metadata(); return new AsyncUnaryCall<T>( task, headers, () => status, () => trailers, () => {}); } }
步骤2:编写单元测试
直接Mock gRPC客户端,指定返回模拟的AsyncUnaryCall:
using Moq; using Xunit; public class ProductControllerTests { private readonly Mock<ProductService.ProductServiceClient> _mockGrpcClient; private readonly ProductController _controller; public ProductControllerTests() { _mockGrpcClient = new Mock<ProductService.ProductServiceClient>(); _controller = new ProductController(_mockGrpcClient.Object); } [Fact] public async Task GetProduct_ValidId_ReturnsOkResult() { // Arrange var testRequest = new GetProductRequest { ProductId = 1 }; var mockResponse = new GetProductResponse { ProductId = 1, Name = "Test Product" }; _mockGrpcClient.Setup(c => c.GetProductByIdAsync( testRequest, null, // 可选的CallOptions null, // 可选的CallCredentials It.IsAny<CancellationToken>())) .Returns(GrpcMockHelpers.CreateMockUnaryCall(mockResponse)); // Act var result = await _controller.GetProduct(1); // Assert var okResult = Assert.IsType<OkObjectResult>(result); var productDto = Assert.IsType<ProductDto>(okResult.Value); Assert.Equal("Test Product", productDto.Name); _mockGrpcClient.Verify(c => c.GetProductByIdAsync( testRequest, null, null, It.IsAny<CancellationToken>()), Times.Once); } }
补充:集成测试方案(可选)
如果需要测试完整的调用链路(包括gRPC服务的真实逻辑),可以用Grpc.AspNetCore.Testing包创建测试服务器,让你的Web API连接到这个测试服务器上的gRPC服务。这种方式属于集成测试,适合验证端到端的流程,但不是纯单元测试。
内容的提问来源于stack exchange,提问作者Craig




