DEV Community

loading...

GraphQL.NET with ASP.NET Integration Tests

Matt Hosking
Love creating systems and solving complex problems. Particularly interested in the dark art of making microservices work for you, not against you.
・3 min read

Supporting code available on GitHub.

ASP.NET Integration Tests

Microsoft have created a really effective way to perform unit tests against the closest thing to a server hosting your deployed code. Don't let the name throw you, though - these tests integrate your code modules together for testing, but aren't integrated to external resources (such as database, APIs or other services). This means that some level of mocking will still be required, but with the benefit that your entire solution (start up process, API end points, serialization, etc.) will be included in the test. It's certainly not as granular as a unit test would normally be, and I'd highly recommend those for testing complex logic, but it's a very useful tool for a higher layer of tests.

Setting Up the Test Server

The simplest way to get running is to use the WebApplicationFactory class and set it up as an xUnit class fixture. This gives you a class like this:

public class GraphQLTests : IClassFixture<WebApplicationFactory<Startup>>
{
  private readonly WebApplicationFactory<Startup> _factory;

  public GraphQLTests(WebApplicationFactory<Startup> factory)
  {
    _factory = factory;
  }

  ...
}
Enter fullscreen mode Exit fullscreen mode

Where Startup is the existing class you use in your main program. From there, you can simply call _factory.CreateClient to give you your HttpClient for testing.

Mocking Dependencies

It's fairly simple to mock interfaces for your services dependent on external APIs and the like, but ideally you would mock only the communication layer and leave all the other service business logic in the test. For Entity Framework, this has an in-memory database provider which is brilliant for this sort of thing. You can seed data up front (in your Arrange phase), let your code perform modifications (in the Act phase) and then verify everything is how you expect it to be (in the Assert phase). So how do you modify your service set up without having to create a new Startup class or have a whole lot of virtuals and overrides in a test class? The WebApplicationFactory class provides the following method to do exactly that:

var factory = _factory.WithWebHostBuilder(builder =>
{
  builder.ConfigureServices(services =>
  {
    services.Replace(ServiceDescriptor.Singleton<IDocumentExecuter, OptimisedDocumentExecuter>());
  });
});
Enter fullscreen mode Exit fullscreen mode

This creates a new factory that applies the additional configuration (in this case, additional service configuration).

Executing GraphQL Queries

The integration test server allows you to create a HttpClient that has a special HttpMessageHandler that, rather than using TCP sockets, communicates directly with the in-memory server. All the usual serialisation still occurs. We can very easily use this to execute a GraphQL query, manually forming a HttpRequestMessage, but to make things a little easier, we can use GraphQL.NET's GraphQL.Client package. With this in play, we can create a simple test using the HttpClient we created earlier:

var graphClient = new GraphQLHttpClient(new GraphQLHttpClientOptions { EndPoint = new Uri("http://localhost/api/graphql") }, new SystemTextJsonSerializer(), httpClient);

var response = await graphClient
  .SendQueryAsync(@"query TestQuery($id: String!) {
  droid(id: $id) {
    id
    name
  }
}",
  new
  {
      id = "3"
  }, "TestQuery", () => new { droid = new DroidResponse() });

response.Errors.Should().BeNullOrEmpty();
response.Data.droid.Name.Should().Be("R2-D2");
Enter fullscreen mode Exit fullscreen mode

I highly recommend you read the GraphQL.Client docs, but the short of it is that you provide the query, variables and operation name (fairly straight-forward), with a final parameter that specifies the "shape" of the the response. This shape provides the structure to use when deserializing the response. Finally I'm using Fluent Assertions to validate some properties of the response.

Discussion (0)