question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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:closed
  • Created 2 years ago
  • Comments:11 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
lamLeXcommented, Apr 4, 2022

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 this

        /// <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"))
                        .ConfigurePrimaryHttpMessageHandler(() => this.Server.CreateHandler());
                })
                ;
0reactions
j2jensencommented, Jan 27, 2023

Just a little warning about ConfigurePrimaryHttpMessageHandler: If your tests ever leverage WithWebHostBuilder 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()).

Read more comments on GitHub >

github_iconTop Results From Across the Web

testing - .NET 6 TestServer cannot resolve Controller
NET 6 minimal API, and I'm using WebApplicationFactory<T> for integration testing. Usually, I have something roughly like this in my tests:
Read more >
Supporting integration tests with WebApplicationFactory in ...
In this post I look at a related change to ensure that integration testing with WebApplicationFactory works in .NET 6.
Read more >
Integration tests in ASP.NET Core
Learn how integration tests ensure that an app's components function correctly at the infrastructure level, including the database, ...
Read more >
Testing in .NET 6 with WebApplicationFactory (including ...
NET Web API application typically consists of testing a single controller method as a function call and avoids having to call the endpoint...
Read more >
Integration Testing ASP.NET Core 6 WebAPI Applications
We create an instance of the WebApplicationFactory type. It's a generic type, and we provide the Program class from the API project as...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found