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

如何在C#中通过CallInvoker模拟gRPC调用(无需启动服务)

解决Grpc.Core内存中测试服务的方案

针对你的问题,完全可以通过自定义CallInvoker实现客户端调用直接定向到内存中的服务实例,无需启动真实gRPC服务或使用网络连接,完美解决测试性能、稳定性和并行执行的问题。

核心思路

Grpc.Core的客户端本质是通过CallInvoker接口发起所有调用,替换这个接口的实现,让它直接调用服务实例的业务方法,绕开网络通道的所有开销。这种方式完全在内存中执行,没有端口占用,支持并行测试。

具体实现

1. 自定义DirectServiceCallInvoker

这个类继承自CallInvoker,负责将客户端调用转发到指定的服务实例:

using Grpc.Core;
using Grpc.Core.Utils;
using System.Reflection;
using System.Collections.Generic;
using System.Threading.Tasks;

public class DirectServiceCallInvoker<TService> : CallInvoker where TService : class
{
    private readonly TService _serviceInstance;

    public DirectServiceCallInvoker(TService serviceInstance)
    {
        _serviceInstance = GrpcPreconditions.CheckNotNull(serviceInstance, nameof(serviceInstance));
    }

    // 处理异步Unary调用(最常用的调用类型)
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        Method<TRequest, TResponse> method,
        string host,
        CallOptions options,
        TRequest request)
    {
        // 匹配服务中对应的方法(gRPC生成的服务方法签名为:Task<TResponse> 方法名(TRequest, ServerCallContext))
        var serviceMethod = typeof(TService).GetMethod(
            method.Name,
            BindingFlags.Public | BindingFlags.Instance,
            new[] { typeof(TRequest), typeof(ServerCallContext) });

        GrpcPreconditions.CheckNotNull(serviceMethod, $"服务类型 {typeof(TService).Name} 未实现方法 {method.Name}");

        // 创建模拟的ServerCallContext,满足服务方法的参数要求
        var mockContext = new MockServerCallContext(options);

        // 调用服务实例的方法
        var responseTask = (Task<TResponse>)serviceMethod.Invoke(_serviceInstance, new object[] { request, mockContext });

        // 包装成gRPC客户端期望的AsyncUnaryCall对象
        return new AsyncUnaryCall<TResponse>(
            responseTask,
            Task.FromResult(new Metadata()),
            () => mockContext.Status,
            () => mockContext.ResponseTrailers,
            () => { });
    }

    // 如需支持流式调用,补充以下方法的实现(示例以ServerStreaming为例)
    public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
    {
        var serviceMethod = typeof(TService).GetMethod(
            method.Name,
            BindingFlags.Public | BindingFlags.Instance,
            new[] { typeof(TRequest), typeof(IServerStreamWriter<TResponse>), typeof(ServerCallContext) });

        GrpcPreconditions.CheckNotNull(serviceMethod, $"服务类型 {typeof(TService).Name} 未实现流式方法 {method.Name}");

        var mockContext = new MockServerCallContext(options);
        var responseStream = new MockServerStreamWriter<TResponse>();

        var invokeTask = serviceMethod.Invoke(_serviceInstance, new object[] { request, responseStream, mockContext }) as Task;

        return new AsyncServerStreamingCall<TResponse>(
            responseStream.AsAsyncStreamReader(),
            Task.FromResult(new Metadata()),
            () => mockContext.Status,
            () => mockContext.ResponseTrailers,
            () => { });
    }

    // 省略ClientStreaming、DuplexStreaming等方法的实现,按需补充
    public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
    {
        throw new NotImplementedException("如需支持客户端流式调用,请补充实现此方法");
    }

    public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
    {
        throw new NotImplementedException("如需支持双向流式调用,请补充实现此方法");
    }

    public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
    {
        return AsyncUnaryCall(method, host, options, request).ResponseAsync.Result;
    }
}

// 模拟ServerCallContext的基础实现
public class MockServerCallContext : ServerCallContext
{
    private readonly CallOptions _callOptions;

    public MockServerCallContext(CallOptions callOptions)
    {
        _callOptions = callOptions;
    }

    public override CancellationToken CancellationToken => _callOptions.CancellationToken;
    public override string Host => "localhost";
    public override string Peer => "test-client";
    public override Metadata RequestHeaders => _callOptions.Headers ?? new Metadata();
    public override Metadata ResponseTrailers { get; } = new Metadata();
    public override Status Status { get; set; } = Status.DefaultSuccess;

    public override Task WriteResponseHeadersAsync(Metadata responseHeaders) => Task.CompletedTask;
}

// 模拟ServerStreamWriter的基础实现(用于流式调用)
public class MockServerStreamWriter<T> : IServerStreamWriter<T>
{
    private readonly List<T> _messages = new List<T>();

    public List<T> WrittenMessages => _messages;

    public Task WriteAsync(T message)
    {
        _messages.Add(message);
        return Task.CompletedTask;
    }

    public WriteOptions WriteOptions { get; set; }

    // 转换为AsyncStreamReader供客户端使用
    public IAsyncStreamReader<T> AsAsyncStreamReader()
    {
        return new MockAsyncStreamReader<T>(_messages);
    }
}

public class MockAsyncStreamReader<T> : IAsyncStreamReader<T>
{
    private readonly IEnumerator<T> _enumerator;

    public MockAsyncStreamReader(IEnumerable<T> messages)
    {
        _enumerator = messages.GetEnumerator();
    }

    public T Current => _enumerator.Current;

    public Task<bool> MoveNext(CancellationToken cancellationToken)
    {
        return Task.FromResult(_enumerator.MoveNext());
    }
}

2. 在测试中使用

直接创建服务实例(可以是真实实现或Mock对象),用自定义CallInvoker初始化客户端,即可实现内存调用:

// 1. 创建服务实例(这里用真实业务实现,也可以用Moq等工具Mock)
var greeterService = new GreeterService();

// 2. 初始化自定义CallInvoker
var callInvoker = new DirectServiceCallInvoker<Greeter.GreeterBase>(greeterService);

// 3. 用CallInvoker创建客户端(替代原来的Channel方式)
var client = new Greeter.GreeterClient(callInvoker);

// 4. 执行测试调用,完全在内存中执行,无网络开销
var request = new HelloRequest { Name = "TestUser" };
var reply = await client.SayHelloAsync(request);

// 5. 断言结果
Assert.AreEqual("Hello TestUser", reply.Message);

方案优势

  • 性能提升:完全内存调用,比网络调用快几个数量级,大幅缩短测试执行时间
  • 稳定性高:避免本地网络波动、端口占用等问题导致的测试失败
  • 支持并行测试:无需绑定端口,多个测试用例可以同时执行,不会有资源冲突
  • 灵活可控:可以直接Mock服务实例,注入测试依赖,轻松模拟各种业务场景

注意事项

  • 该方案兼容Grpc.Core 2.30.0版本,CallInvoker在该版本中是公开可继承的
  • 流式调用(Server/Client/Duplex)需要补充对应方法的实现,示例中提供了ServerStreaming的基础实现,可按需扩展
  • MockServerCallContext可以根据测试需求扩展更多属性(比如模拟认证信息、自定义请求头等)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:05:20