Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support GraphQL’s transport layer agnostics #14

Open
DaveRMaltby opened this issue Oct 31, 2022 · 7 comments
Open

Support GraphQL’s transport layer agnostics #14

DaveRMaltby opened this issue Oct 31, 2022 · 7 comments

Comments

@DaveRMaltby
Copy link
Contributor

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
Copy link
Contributor Author

Created PR #15 to solve this issue.

@byme8
Copy link
Owner

byme8 commented Oct 31, 2022

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
Copy link
Contributor Author

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
Copy link
Owner

byme8 commented Nov 1, 2022

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
Copy link
Contributor Author

DaveRMaltby commented Nov 1, 2022

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
Copy link

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
Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants