Compartir a través de


Prueba de servicios gRPC en ASP.NET Core

Por James Newton-King

Las pruebas son un aspecto importante de la creación de software estable y fácil de mantener. En este artículo se describe cómo probar servicios gRPC en ASP.NET Core.

Hay tres enfoques comunes para probar los servicios gRPC:

  • Pruebas unitarias: pruebe los servicios gRPC directamente desde una biblioteca de pruebas unitarias.
  • Prueba de integración: la aplicación gRPC se hospeda en TestServer, un servidor de pruebas en memoria del paquete Microsoft.AspNetCore.TestHost. Los servicios gRPC se prueban llamándolos mediante un cliente gRPC desde una biblioteca de pruebas unitarias.
  • Pruebas manuales: pruebe servidores gRPC con llamadas ad hoc. Para obtener información sobre cómo usar la línea de comandos y las herramientas de interfaz de usuario con servicios gRPC, vea Comprobación de servicios gRPC con gRPCurl y gRPCui en ASP.NET Core.

En las pruebas unitarias, solo está implicado el servicio gRPC. Las dependencias que se insertan en el servicio deben simularse. En las pruebas de integración, el servicio gRPC y su infraestructura auxiliar forman parte de la prueba. Esto incluye el inicio de la aplicación, la inserción de dependencias, el enrutamiento, la autenticación y la autorización.

Ejemplo de servicio que se puede probar

Para demostrar las pruebas de servicio, revise el siguiente servicio en la aplicación de ejemplo.

Vea o descargue el código de ejemplo (cómo descargarlo)

TesterService devuelve saludos mediante cuatro tipos de método de gRPC.

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

El servicio gRPC anterior:

Servicios gRPC de prueba unitaria

Una biblioteca de pruebas unitarias puede probar directamente los servicios gRPC llamando a sus métodos. Las pruebas unitarias prueban un servicio gRPC de forma aislada.

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

La prueba unitaria anterior:

  • Simula IGreeter mediante Moq.
  • Ejecuta el método SayHelloUnary con un mensaje de solicitud y un elemento ServerCallContext. Todos los métodos de servicio tienen un argumento ServerCallContext. En esta prueba, el tipo se proporciona mediante el método auxiliar TestServerCallContext.Create(). Este método auxiliar se incluye en el código de ejemplo.
  • Realiza aserciones:
    • Comprueba que el nombre de la solicitud se pasa a IGreeter.
    • El servicio devuelve el mensaje de respuesta esperado.

Prueba unitaria HttpContext en métodos gRPC

Los métodos gRPC pueden acceder a la clase HttpContext de una solicitud mediante el método de extensión ServerCallContext.GetHttpContext. Para realizar una prueba unitaria de un método que usa HttpContext, el contexto debe configurarse en la configuración de la prueba. Si HttpContext no está configurado, GetHttpContext devuelve null.

Para configurar un parámetro HttpContext durante la configuración de la prueba, cree una instancia y agrégrela a la colección ServerCallContext.UserState mediante la clave __HttpContext.

var httpContext = new DefaultHttpContext();

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

Ejecute métodos de servicio con este contexto de llamada para usar la instancia de HttpContext configurada.

Servicios gRPC de pruebas de integración

Las pruebas de integración evalúan los componentes de una aplicación en un nivel más amplio que las pruebas unitarias. La aplicación gRPC se hospeda en TestServer, un servidor de pruebas en memoria del paquete Microsoft.AspNetCore.TestHost.

Una biblioteca de pruebas unitarias inicia la aplicación gRPC y, a continuación, los servicios gRPC se prueban mediante el cliente gRPC.

El código de ejemplo contiene la infraestructura para que las pruebas de integración puedan realizarse:

  • La clase GrpcTestFixture<TStartup> configura el host de ASP.NET Core e inicia la aplicación gRPC en un servidor de prueba en memoria.
  • La clase IntegrationTestBase es el tipo base del que heredan las pruebas de integración. Contiene el estado del accesorio y las API para crear un cliente gRPC para llamar a la aplicación gRPC.
[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);
}

La prueba de integración anterior:

  • Crea un cliente gRPC mediante el canal proporcionado por IntegrationTestBase. Este tipo se incluye en el código de ejemplo.
  • Llama al método SayHelloUnary mediante el cliente gRPC.
  • Declara que el servicio devuelve el mensaje de respuesta esperado.

Inserción de dependencias simuladas

Use ConfigureWebHost en el accesorio para invalidar las dependencias. Invalidar dependencias es útil cuando una dependencia externa no está disponible en el entorno de prueba. Por ejemplo, una aplicación que usa una puerta de enlace de pago externa no debe llamar a la dependencia externa al ejecutar pruebas. En su lugar, use una puerta de enlace simulada para la prueba.

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

La prueba de integración anterior:

  • En el constructor (MockedGreeterServiceTests) de la clase de prueba:
    • Simula IGreeter mediante Moq.
    • Invalida el elemento IGreeter registrado con la inserción de dependencias mediante ConfigureWebHost.
  • Llama al método SayHelloUnary mediante el cliente gRPC.
  • Declara el mensaje de respuesta esperado basado en la instancia IGreeter simulada.

Recursos adicionales