Functional test with WebApplicationFactory and AddGrpcClient can't reach the TestServer
See original GitHub issue@JamesNK wrote a very good example to guide us how to do the functional test for grpc service here https://github.com/grpc/grpc-dotnet/tree/master/examples/Tester. It includes two major parts:
- Setting up a TestServer
- Setting up a GRPC channel that uses the HttpMessageHandler created by TestServer.
However, it’s quite some code to maintain. I decided to take different approach that uses WebApplicationFactory
and grpc client factory integration (via AddGrpcClient
). My test fixture is boiled down to this single class
namespace Tests.FunctionalTests.GrpcTestHelpers
{
using System;
using Grpc.Core;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
public class GrpcWebApplicationFactory<TStartup, TClient> : WebApplicationFactory<TStartup>
where TStartup : class
where TClient : ClientBase<TClient>
{
#region Overrides of WebApplicationFactory<T>
/// <summary>
/// Gives a fixture an opportunity to configure the application before it gets built.
/// </summary>
/// <param name="builder">The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" /> for the application.</param>
protected override void ConfigureWebHost(IWebHostBuilder builder) =>
builder
.UseEnvironment("Test")
.ConfigureServices(services =>
services.AddGrpcClient<TClient>(options =>
{
options.Address = new Uri("http://localhost");
}))
;
#endregion
public TClient RetrieveGrpcClient()
{
return this.Services.GetRequiredService<TClient>();
}
}
}
And here is my test class using xunit
public class GreeterServiceTests
: IClassFixture<GrpcWebApplicationFactory<Startup, Greeter.GreeterClient>>
{
private readonly GrpcWebApplicationFactory<Startup, Greeter.GreeterClient> factory;
/// <summary>
/// Initializes a new instance of the <see cref="GreeterServiceTests"/> class.
/// </summary>
/// <param name="factory">The factory.</param>
public GreeterServiceTests(GrpcWebApplicationFactory<Startup, Greeter.GreeterClient> factory)
=> this.factory = factory;
[Fact]
public async Task SayHelloUnaryTest()
{
// Arrange
var client = this.factory.RetrieveGrpcClient();
// Act
var response = await client.SayHelloUnaryAsync(new HelloRequest { Name = "Joe" });
// Assert
response.Message.Should().Be("Hello Joe");
}
}
When I run the test, I encountered this exception
Grpc.Core.RpcException
Status(StatusCode="Unavailable", Detail="Error starting gRPC call. HttpRequestException: No connection could be made because the target machine actively refused it. (localhost:80) SocketException: No connection could be made because the target machine actively refused it.", DebugException="System.Net.Http.HttpRequestException: No connection could be made because the target machine actively refused it. (localhost:80)
---> System.Net.Sockets.SocketException (10061): No connection could be made because the target machine actively refused it.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
......
My gut told me that the TestServer may not start yet. Digging a bit deeper into the WebApplicationFactory.CreateClient()
, I found that it makes sure the TestServer running via private EnsureServer()
method. I decided to call the CreateClient()
to trigger this EnsureServer()
side effect like this
public TClient RetrieveGrpcClient()
{
this.CreateClient();
return this.Services.GetRequiredService<TClient>();
}
I still received the same exception. Finally, I decided to replace the HttpClient instance of the grpcClient like this
protected override void ConfigureWebHost(IWebHostBuilder builder) =>
builder
.UseEnvironment("Test")
.ConfigureServices(services =>
services.AddGrpcClient<TClient>(options =>
{
options.ChannelOptionsActions.Add(channelOptions =>
{
//Set the handler to null
//so that we could use the HttpClient created by this WebApplicationFactory
channelOptions.HttpHandler = null;
channelOptions.HttpClient = this.CreateClient();
});
options.Address = new Uri("http://localhost");
}))
;
My test finally passed.
I’m keen to understand this behavior. Is it possibly a bug in WebApplicationFactory
or is it a bug in AddGrpcClient
? If all are working as designed, is this a correct way to set it up?
Issue Analytics
- State:
- Created 2 years ago
- Comments:11 (4 by maintainers)
Top GitHub Comments
Hi @rdcm, I have already provided the example in my original post. The only change to make it work is to
ConfigurePrimaryHttpMessageHandler
as James suggested. So the final code should look like thisJust a little warning about
ConfigurePrimaryHttpMessageHandler
: If your tests ever leverageWithWebHostBuilder
for customizations, that method returns a new derived factory instance.this.Server
refers to the current Factory instance, and will automatically ensure the server is booted up, so.ConfigurePrimaryHttpMessageHandler(() => this.Server.CreateHandler())
can cause a second instance of the web host to boot up, with all of the startup logic in your Program getting executed again. Instead, you can use.ConfigurePrimaryHttpMessageHandler(services => ((TestServer)services.GetRequiredService<IServer>()).CreateHandler())
.