using Grpc.Core; using Grpc.Net.Client; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Diagnostics; using Xunit.Abstractions; namespace TestGrpc.Test; public class UnitTest1 : IntegrationTestBase { public UnitTest1(GrpcTestFixture fixture, ITestOutputHelper outputHelper) : base(fixture, outputHelper) { } [Fact] public async Task Test1() { // Arrange var client = new Greeter.GreeterClient(Channel); // Act using var response = client.DownloadFile(new FileRequest { FilePath = "Files/test.txt" }); using var memoryStream = new MemoryStream(); var receivedFileName = string.Empty; await foreach (var chunk in response.ResponseStream.ReadAllAsync()) { if (!string.IsNullOrEmpty(chunk.FileName)) { receivedFileName = chunk.FileName; } memoryStream.Write(chunk.Chunk.Span); } memoryStream.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader(memoryStream); while (!reader.EndOfStream) { var line = await reader.ReadLineAsync(); Console.WriteLine(line); } // Assert Assert.Equal("test.txt", receivedFileName); } } internal class ForwardingLoggerProvider : ILoggerProvider { private readonly LogMessage _logAction; public ForwardingLoggerProvider(LogMessage logAction) { _logAction = logAction; } public ILogger CreateLogger(string categoryName) { return new ForwardingLogger(categoryName, _logAction); } public void Dispose() { } internal class ForwardingLogger : ILogger { private readonly string _categoryName; private readonly LogMessage _logAction; public ForwardingLogger(string categoryName, LogMessage logAction) { _categoryName = categoryName; _logAction = logAction; } public IDisposable BeginScope(TState state) { return null!; } public bool IsEnabled(LogLevel logLevel) { return true; } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { _logAction(logLevel, _categoryName, eventId, formatter(state, exception), exception); } } } internal class GrpcTestContext : IDisposable where TStartup : class { private readonly Stopwatch _stopwatch; private readonly GrpcTestFixture _fixture; private readonly ITestOutputHelper _outputHelper; public GrpcTestContext(GrpcTestFixture fixture, ITestOutputHelper outputHelper) { _stopwatch = Stopwatch.StartNew(); _fixture = fixture; _outputHelper = outputHelper; _fixture.LoggedMessage += WriteMessage; } private void WriteMessage(LogLevel logLevel, string category, EventId eventId, string message, Exception? exception) { var log = $"{_stopwatch.Elapsed.TotalSeconds:N3}s {category} - {logLevel}: {message}"; if (exception != null) { log += Environment.NewLine + exception.ToString(); } _outputHelper.WriteLine(log); } public void Dispose() { _fixture.LoggedMessage -= WriteMessage; } } public delegate void LogMessage(LogLevel logLevel, string categoryName, EventId eventId, string message, Exception? exception); public class GrpcTestFixture : IDisposable where TStartup : class { private TestServer? _server; private IHost? _host; private HttpMessageHandler? _handler; private Action? _configureWebHost; public event LogMessage? LoggedMessage; public GrpcTestFixture() { LoggerFactory = new LoggerFactory(); LoggerFactory.AddProvider(new ForwardingLoggerProvider((logLevel, category, eventId, message, exception) => { LoggedMessage?.Invoke(logLevel, category, eventId, message, exception); })); } public void ConfigureWebHost(Action configure) { _configureWebHost = configure; } private void EnsureServer() { if (_host == null) { var builder = new HostBuilder() .ConfigureServices(services => { services.AddSingleton(LoggerFactory); }) .ConfigureWebHostDefaults(webHost => { webHost .UseTestServer() .UseStartup(); _configureWebHost?.Invoke(webHost); }); _host = builder.Start(); _server = _host.GetTestServer(); _handler = _server.CreateHandler(); } } public LoggerFactory LoggerFactory { get; } public HttpMessageHandler Handler { get { EnsureServer(); return _handler!; } } public void Dispose() { _handler?.Dispose(); _host?.Dispose(); _server?.Dispose(); } public IDisposable GetTestContext(ITestOutputHelper outputHelper) { return new GrpcTestContext(this, outputHelper); } } public class IntegrationTestBase : IClassFixture>, IDisposable { private GrpcChannel? _channel; private IDisposable? _testContext; protected GrpcTestFixture Fixture { get; set; } protected ILoggerFactory LoggerFactory => Fixture.LoggerFactory; protected GrpcChannel Channel => _channel ??= CreateChannel(); protected GrpcChannel CreateChannel() { return GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions { LoggerFactory = LoggerFactory, HttpHandler = Fixture.Handler }); } public IntegrationTestBase(GrpcTestFixture fixture, ITestOutputHelper outputHelper) { Fixture = fixture; _testContext = Fixture.GetTestContext(outputHelper); } public void Dispose() { _testContext?.Dispose(); _channel = null; } }