You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

.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

火山引擎 最新活动