Search code examples
.net-coregrpcflurl

Use HttpTest inside asp.net core/grpc integration test


I have a ASP.NET core/gRPC service that I want to integration test. Inside the grpc services I make Flurl calls to 3rd party web apis. I want to mock those calls out using Flurl's HttpTest class. I can get it to work in a really convoluted way but it must be easier.

Currently:

            var client = _factory
                    .WithWebHostBuilder(builder =>
                    {
                        builder.ConfigureTestServices(services =>
                        {
                        });
                    })
                .CreateClient(new WebApplicationFactoryClientOptions 
                {BaseAddress = new Uri("http://localhost:5555")});

            using var channel = GrpcChannel.ForAddress("http://localhost:5555",
                new GrpcChannelOptions { HttpClient = client });

            var grpcClient = new MyGrpcService.MyClient(channel);

            var httpTest = new HttpTest();
            var content = new StringContent("some removed json", Encoding.UTF8, "application/json");
            httpTest.RespondWith(content, 200);

...
            var resp = await grpcClient.GetSomeDataAsync(someRequest);

n.b. I'm creating my IFlurlClient manually inside the service as we need to provide add dynamic certificate dependent on the host.

My main problem is that Flurl is making real HTTP calls rather than using the HttpTest.

After some digging, this is due to the static nature of HttpTest.Current. The static HttpTest.Current that Flurl uses is set up in the test but does not exist inside the server. I've managed to hack it working by relaying the outer HttpTest.ResponseQueue to one inside the service via a DI override of FlurlClientFactoryBase but this seems far from ideal.

Is there a canonical way of integration testing a service and having Flurl's HttpTest mock any outgoing HTTP calls?


Solution

  • In order for Flurl to signal (to itself, effectively) to fake all calls during the existence of an HttpTest object, without resorting to a static context (which can mess up parallel tests), it needs some context to flow this information from the test, through your SUT, and into the library bits of Flurl. In .NET Core, the mechanism to do that is AsyncLocal<T>, which is a logical call context that can flow to async continuations.

    However, one boundary it can't flow across by default is TestServer. This is by design, as announced here. Although I haven't tried it, that announcement seems to imply that you could get this working if you create your test client like this:

    var server = new TestServer(webHostBuilder) 
    {
        PreserveExecutionContext = true;
    };
    var client = server.CreateClient();