ASP.NET Core で gRPC サービスをテストする

作成者: James Newton-King

テストは、安定し、保守性に優れたソフトウェアを構築する上で重要な作業です。 この記事では、ASP.NET Core の gRPC サービスをテストする方法について説明します。

gRPC サービスをテストする場合、3 つの一般的な方法があります。

  • 単体テスト: 単体テスト ライブラリから gRPC サービスを直接テストします。
  • 統合テスト: gRPC アプリは、Microsoft.AspNetCore.TestHost パッケージのメモリ内テスト サーバーである TestServer でホストされています。 gRPC サービスは、単体テスト ライブラリの gRPC クライアントを使用して呼び出すことでテストされます。
  • 手動テスト: アドホック呼び出しで gRPC サーバーをテストします。 gRPC サービスでコマンドラインおよび UI ツールを使用する方法については、「ASP.NET Core で gRPCurl と gRPCui を使用して gRPC サービスをテストする」を参照してください。

単体テストでは、gRPC サービスだけが関与します。 サービスに挿入される依存関係はモックする必要があります。 統合テストでは、gRPC サービスとその補助インフラストラクチャがテストに含まれます。 これには、アプリの起動、依存関係の挿入、ルーティングと認証、承認が含まれます。

テスト可能なサービスの例

サービスのテストを示すために、サンプル アプリで次のサービスを確認します。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

TesterService は、gRPC の 4 つのメソッドの型を使用して応答を返します。

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 のインスタンスを提供します。
  • モック オブジェクト フレームワークを使用するモックされた IGreeter サービスでテストできます (Moq サービスなど)。 モック オブジェクトは、テストで使用されるプロパティとメソッドの動作が事前定義されている加工オブジェクトです。 詳細については、「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 が構成されていない場合、GetHttpContextnull が返されます。

テストの設定時に HttpContext を構成するには、新しいインスタンスを作成し、__HttpContext キーを使用して ServerCallContext.UserState コレクションに追加します。

var httpContext = new DefaultHttpContext();

var serverCallContext = TestServerCallContext.Create();
serverCallContext.UserState["__HttpContext"] = httpContext;

構成済みの HttpContext インスタンスを使用するこの呼び出しコンテキストでサービス メソッドを実行します。

gRPC サービスの統合テスト

統合テストでは 単体テストよりも広範なレベルでアプリのコンポーネントを評価します。 gRPC アプリは、Microsoft.AspNetCore.TestHost パッケージのメモリ内テスト サーバーである TestServer でホストされています。

単体テスト ライブラリによって 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 をオーバーライドします。
  • gRPC クライアントを使用して SayHelloUnary メソッドを呼び出します。
  • モック IGreeter インスタンスに基づいて、予期される応答メッセージをアサートします。

その他のリソース