byme8 / ZeroQL

C# GraphQL client with Linq-like syntax
MIT License
272 stars 13 forks source link

Support GraphQL’s transport layer agnostics #14

Open DaveRMaltby opened 1 year ago

DaveRMaltby commented 1 year ago

For integration testing of our product which uses Hot Chocolate, I am not standing up an HTTP endpoint in order to make GraphQL calls. Hot Chocolate exposes an IRequestExecutor.ExecuteAsync() method which allows us to avoid HTTP altogether and just provide json back and forth.
Initially when I began to use your library, I believed that I could use the generated ZeroQL classes based on my schema and just create my own version of ZeroQL.GraphQLClient<> which didn't require a System.Net.Http.HttpClient. Of course, I also had to copy the GraphQLClientLambdaExtensions.cs code and modify it to use my new GraphQLClient derivative. Anyhow, I hit a deadend without making changes to the ZeroQL library in that the ZeroQL code anaylizers are coming back with a DiagnosticDescriptor of Descriptors.FailedToConvert. I believe that there is some conversion that is depending on a client inherited from ZeroQL.GraphQLClient<>. Anyhow, seems like changes in ZeroQL are needed to accomplish what I'm hoping to do.

I have already forked and created a branch that works in the unit test that I created. The PR will follow momentarily.

DaveRMaltby commented 1 year ago

Created PR #15 to solve this issue.

byme8 commented 1 year ago

I had a quick look at your PR. As for me, this implementation contains too much of the "leaky abstraction". The relations between abstractions depend on the knowledge that it is not an HTTP transport. When ideally, it should not care about it.

I am going to have a more deep look at PR later on and also will think about other ways to support your use case.

DaveRMaltby commented 1 year ago

I understand your concern and have the same concerns in mind too. With this issue having so many touch points into your codebase, I held back on trying to do too much to address these concerns that you're bringing up. But on the other hand, I didn't want to purpose this significate feature request without providing some type of solution. Thanks for looking into it.

byme8 commented 1 year ago

Solution for your use case:


  public static async Task<IGraphQLResult> Execute()
  {
      var serviceCollection = new ServiceCollection();
      var server = TestServer.Program.AddGraphQL(serviceCollection);
      var excutor = await server.BuildRequestExecutorAsync();

      var httpClient = new HttpClient(new HotChocoClientHandler(excutor))
      {
          BaseAddress = new Uri("http://localhost:10000/graphql")
      };

      var qlClient = new TestServerClient(httpClient);
      // place to replace
      var response = await qlClient.Query(static q => q.Me(o => o.FirstName));

      return response;
  }

public class HotChocoClientHandler : HttpClientHandler
{
    private readonly IRequestExecutor executor;

    public HotChocoClientHandler(IRequestExecutor executor)
    {
        this.executor = executor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage httpRequest, CancellationToken cancellationToken)
    {
        var requestJson = await httpRequest.Content!.ReadAsStringAsync(cancellationToken);
        var request = JsonConvert.DeserializeObject<GraphQLRequest>(requestJson);
        var executionResult = await executor.ExecuteAsync(request.Query, cancellationToken);
        var json = await executionResult.ToJsonAsync();

        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new ReadOnlyMemoryContent(Encoding.UTF8.GetBytes(json))
        };
    }
}

About different transport layers, it is more complicated. At some point, I want to experiment and replace JSON with MessagePack or Protobuff. It would be an excellent time to look at transport layers.

DaveRMaltby commented 1 year ago

Thank you sir! Looks like a great workaround that should let my project return to using your NuGet packages (instead of a fork). Appreciate it! I was unaware about the HttpClientHandler class. Clever.

kadamgreene commented 1 month ago

I know this is an old thread but if your HotChocolate server is .NET 6+, you could spin up the server using Microsoft's WebApplicationFactory and use the HttpClient it generates. This creates an integration tests rather than a unit test.

https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests

DaveRMaltby commented 1 month ago

I know this is an old thread but if your HotChocolate server is .NET 6+, you could spin up the server using Microsoft's WebApplicationFactory and use the HttpClient it generates. This creates an integration tests rather than a unit test.

https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests

@kadamgreene, thanks for this suggestion and link. I was unaware of this approach. Currently, I'm relying on the approach that @byme8 suggested, but will consider this too, in new work. Appreciate you rekindling this old thread.