Nguon: Microsoft Learn · .NET 8.0

Kiểm thử dịch vụ gRPC trong ASP.NET Core

Nguồn: Test gRPC services in ASP.NET Core

Bởi James Newton-King

Kiểm thử là một khía cạnh quan trọng trong việc xây dựng phần mềm ổn định và có thể bảo trì. Bài viết này thảo luận về cách kiểm thử các dịch vụ gRPC trong ASP.NET Core.

Có ba cách tiếp cận phổ biến để kiểm thử dịch vụ gRPC:

Trong unit testing, chỉ có dịch vụ gRPC tham gia. Các dependency (phụ thuộc) được inject vào dịch vụ phải được mock (giả lập). Trong integration testing, dịch vụ gRPC và cơ sở hạ tầng phụ trợ của nó đều là một phần của bài kiểm thử. Điều này bao gồm khởi động ứng dụng, dependency injection (DI), routing (định tuyến), authentication (xác thực) và authorization (phân quyền).

Dịch vụ mẫu có thể kiểm thử

Để minh họa việc kiểm thử dịch vụ, hãy xem xét dịch vụ sau trong ứng dụng mẫu.

Xem hoặc tải xuống code mẫu

TesterService trả về lời chào sử dụng bốn loại phương thức của gRPC.

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

Dịch vụ gRPC trước đó:

Unit test dịch vụ gRPC

Thư viện unit test có thể kiểm thử trực tiếp các dịch vụ gRPC bằng cách gọi các phương thức của nó. Unit test kiểm thử một dịch vụ gRPC một cách độc lập.

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

Unit test trước đó:

Unit test HttpContext trong các phương thức gRPC

Các phương thức gRPC có thể truy cập HttpContext của request sử dụng extension method ServerCallContext.GetHttpContext. Để unit test một phương thức sử dụng HttpContext, context phải được cấu hình trong thiết lập kiểm thử. Nếu HttpContext không được cấu hình thì GetHttpContext trả về null.

Để cấu hình HttpContext trong thiết lập kiểm thử, tạo một instance mới và thêm nó vào collection ServerCallContext.UserState bằng khóa __HttpContext.

csharp
var httpContext = new DefaultHttpContext();

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

Thực thi các phương thức dịch vụ với call context này để sử dụng instance HttpContext đã được cấu hình.

Integration test dịch vụ gRPC

Integration test đánh giá các thành phần của ứng dụng ở mức độ rộng hơn unit test. Ứng dụng gRPC được lưu trú trong TestServer, một in-memory test server từ gói Microsoft.AspNetCore.TestHost.

Thư viện unit test khởi động ứng dụng gRPC và sau đó các dịch vụ gRPC được kiểm thử bằng gRPC client.

Code mẫu chứa cơ sở hạ tầng để làm cho integration testing có thể thực hiện:

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

Integration test trước đó:

Inject mock dependencies (inject phụ thuộc giả lập)

Sử dụng ConfigureWebHost trên fixture để ghi đè các dependency. Việc ghi đè dependency hữu ích khi external dependency (phụ thuộc bên ngoài) không khả dụng trong môi trường kiểm thử. Ví dụ, ứng dụng sử dụng payment gateway (cổng thanh toán) bên ngoài không nên gọi phụ thuộc bên ngoài khi thực thi kiểm thử. Thay vào đó, hãy sử dụng mock gateway cho bài kiểm thử.

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

Integration test trước đó: