Teilen über


Testen von gRPC-Diensten in ASP.NET Core

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Warnung

Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Von James Newton-King

Testen ist ein wichtiger Aspekt beim Erstellen stabiler und verwaltbarer Software. In diesem Artikel wird erläutert, wie Sie gRPC-Dienste in ASP.NET Core testen.

Es gibt drei gängige Ansätze zum Testen von gRPC-Diensten:

  • Komponententests: Testen Sie gRPC-Dienste direkt in einer Komponententestbibliothek.
  • Integrationstests: Die gRPC-App wird in TestServer gehostet, einem InMemory-Testserver im Paket Microsoft.AspNetCore.TestHost. gRPC-Dienste werden getestet, indem sie mithilfe eines gRPC-Clients aus einer Komponententestbibliothek aufgerufen werden.
  • Manuelles Testen: Testen Sie gRPC-Server mit Ad-hoc-Aufrufen. Informationen zur Verwendung von Befehlszeilen- und Benutzeroberflächentools mit gRPC-Diensten finden Sie unter Testen von gRPC-Diensten mit gRPCurl und gRPCui in ASP.NET Core.

An Komponententests ist nur der gRPC-Dienst beteiligt. In den Dienst eingefügte Abhängigkeiten müssen simuliert werden. Bei Integrationstests sind der gRPC-Dienst und seine zusätzliche Infrastruktur Teil des Tests. Dazu gehören App-Start, Abhängigkeitsinjektion, Routing, Authentifizierung und Autorisierung.

Beispiel eines testbaren Diensts

Zur Veranschaulichung des Testens von Diensten sollten Sie sich den folgenden Dienst in der Beispiel-App ansehen.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)

TesterService gibt Begrüßungen mithilfe der vier Methodentypen von gRPC zurück.

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) });
        }
    }
}

Für den vorherigen gRPC-Dienst gilt Folgendes:

Komponententests von gRPC-Diensten

Eine Komponententestbibliothek kann gRPC-Dienste direkt testen, indem sie ihre Methoden aufruft. Komponententests testen einen gRPC-Dienst isoliert.

[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);
}

Für den vorherigen Komponententest gilt Folgendes:

  • Simuliert IGreeter mithilfe von Moq.
  • Führt die SayHelloUnary-Methode mit einer Anforderungsnachricht und ServerCallContext aus. Alle Dienstmethoden verfügen über das Argument ServerCallContext. In diesem Test wird der Typ mithilfe der Hilfsmethode TestServerCallContext.Create() bereitgestellt. Diese Hilfsmethode ist im Beispielcode enthalten.
  • Macht Assertionen:
    • Überprüft, ob der Anforderungsname an IGreeter übergeben wird.
    • Der Dienst gibt die erwartete Antwortnachricht zurück.

Komponententest HttpContext in gRPC-Methoden

gRPC-Methoden können mit der ServerCallContext.GetHttpContext Erweiterungsmethode auf HttpContext einer Anforderung zugreifen. Zum Komponententest einer Methode, die HttpContext verwendet, muss der Kontext beim Einrichten des Tests konfiguriert werden. Wenn HttpContext nicht konfiguriert ist, wird null von GetHttpContext zurückgegeben.

Um HttpContext während der Testeinrichtung zu konfigurieren, erstellen Sie eine neue Instanz und fügen sie mit dem Schlüssel __HttpContext zur Sammlung ServerCallContext.UserState hinzu.

var httpContext = new DefaultHttpContext();

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

Führen Sie Methoden des Diensts mit diesem Aufrufkontext aus, um die konfigurierte HttpContext-Instanz zu verwenden.

Integrationstest für gRPC-Dienste

Integrationstests bewerten die Komponenten einer App auf breiterer Ebene als Komponententests. Die gRPC-App wird in TestServer gehostet, einem InMemory-Testserver im Paket Microsoft.AspNetCore.TestHost.

Eine Komponententestbibliothek startet die gRPC-App, und anschließend werden die gRPC-Dienste mit dem gRPC-Client getestet.

Der Beispielcode enthält die Infrastruktur, um Integrationstests zu ermöglichen:

  • Die GrpcTestFixture<TStartup>-Klasse konfiguriert den ASP.NET Core-Host und startet die gRPC-App in einem InMemory-Testserver.
  • Die IntegrationTestBase-Klasse ist der Basistyp, von dem Integrationstests erben. Sie enthält den Fixturezustand und APIs zum Erstellen eines gRPC-Clients zum Aufrufen der gRPC-App.
[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);
}

Für den vorherigen Integrationstest gilt Folgendes:

  • Erstellt einen gRPC-Client unter Verwendung des von IntegrationTestBase bereitgestellten Kanals. Dieser Typ ist im Beispielcode enthalten.
  • Ruft die SayHelloUnary-Methode mithilfe des gRPC-Clients auf.
  • Bestätigt, dass der Dienst die erwartete Antwortnachricht zurückgibt.

Einfügen von Pseudoabhängigkeiten

Verwenden Sie ConfigureWebHost für die Fixture, um Abhängigkeiten zu überschreiben. Das Überschreiben von Abhängigkeiten ist nützlich, wenn eine externe Abhängigkeit in der Testumgebung nicht verfügbar ist. Beispielsweise sollte eine App, die ein externes Zahlungsgateway verwendet, beim Ausführen von Tests die externe Abhängigkeit nicht aufrufen. Verwenden Sie stattdessen ein Pseudogateway für den Test.

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);
}

Für den vorherigen Integrationstest gilt Folgendes:

  • Im Konstruktor der Testklasse (MockedGreeterServiceTests):
    • Simuliert IGreeter mithilfe von Moq.
    • Überschreibt die mit Dependency Injection registrierten IGreeter mit ConfigureWebHost.
  • Ruft die SayHelloUnary-Methode mithilfe des gRPC-Clients auf.
  • Bestätigt die erwartete Antwortnachricht basierend auf der simulierten Instanz IGreeter.

Zusätzliche Ressourcen