Skip to main content

HotChocolate GraphQL–Integration tests

The original way I wrote integration tests for my GraphQL endpoints was inspired by this blog post: https://chillicream.com/blog/2019/04/11/integration-tests

As explained in this post you can create a schema instance and directly invoke it:

public class GraphQLTests:BaseTests {
public GraphQLTests(ITestOutputHelper helper):base(helper)
{
}
protected override void BuildUpContainer(ContainerBuilder containerBuilder)
{
var serviceCollection = new ServiceCollection();
new Startup().ConfigureServices(serviceCollection);
containerBuilder.Populate(serviceCollection);
base.BuildUpContainer(containerBuilder);
}
[Theory]
[InlineData("zoekLandbouwers")]
public async Task Queries_Succeed(string queryName)
{
var serviceProvider = new AutofacServiceProvider(_container);
var schema=serviceProvider.GetRequiredService<ISchema>();
var executor = schema.MakeExecutable();
var query = File.ReadAllText($"GraphQLQueries\\{queryName}.graphql");
IReadOnlyQueryRequest request =
QueryRequestBuilder.New()
.SetQuery(query)
.SetServices(serviceProvider)
.Create();
// act
IExecutionResult result = await executor.ExecuteAsync(request);
// assert
result.ShouldMatchChildSnapshot(queryName);
}
}

However this approach no longer worked after upgrading to HotChocolate 12.  It started to fail with the following error message:

Autofac.Core.Registration.ComponentNotRegisteredException : The requested service 'HotChocolate.ISchema' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.

I tried multiple approaches to get rid of this error but after a few attempts I rewrote the tests to use the Microsoft.AspNetCore.Mvc.Testing package.

I copied a few Helper classes that I found in the HotChocolate repo to make this easier in combination with GraphQL.

using Newtonsoft.Json;
using System.Collections.Generic;
using System.Text;
namespace GraphQL.Tests
{
public class ClientQueryRequest
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("operationName")]
public string OperationName { get; set; }
[JsonProperty("query")]
public string Query { get; set; }
[JsonProperty("variables")]
public Dictionary<string, object> Variables { get; set; }
[JsonProperty("extensions")]
public Dictionary<string, object> Extensions { get; set; }
public override string ToString()
{
var query = new StringBuilder();
if (Id is not null)
{
query.Append($"id={Id}");
}
if (Query is not null)
{
if (Id is not null)
{
query.Append("&");
}
query.Append($"query={Query.Replace("\r", "").Replace("\n", "")}");
}
if (OperationName is not null)
{
query.Append($"&operationName={OperationName}");
}
if (Variables is not null)
{
query.Append("&variables=" + JsonConvert.SerializeObject(Variables));
}
if (Extensions is not null)
{
query.Append("&extensions=" + JsonConvert.SerializeObject(Extensions));
}
return query.ToString();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace GraphQL.Tests
{
public class ClientQueryResult
{
public string ContentType { get; set; }
public HttpStatusCode StatusCode { get; set; }
public Dictionary<string, object> Data { get; set; }
public List<Dictionary<string, object>> Errors { get; set; }
public Dictionary<string, object> Extensions { get; set; }
}
}
using Microsoft.AspNetCore.TestHost;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace GraphQL.Tests
{
public static class HttpClientExtensions
{
public static async Task<ClientQueryResult> PostAsync(
this HttpClient client,
ClientQueryRequest request,
string path = "/graphql")
{
HttpResponseMessage response =
await SendPostRequestAsync(
client,
JsonConvert.SerializeObject(request),
path);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return new ClientQueryResult { StatusCode = HttpStatusCode.NotFound };
}
var json = await response.Content.ReadAsStringAsync();
ClientQueryResult result = JsonConvert.DeserializeObject<ClientQueryResult>(json);
result.StatusCode = response.StatusCode;
result.ContentType = response.Content.Headers.ContentType!.ToString();
return result;
}
public static async Task<IReadOnlyList<ClientQueryResult>> PostAsync(
this HttpClient client,
IReadOnlyList<ClientQueryRequest> request,
string path = "/graphql")
{
HttpResponseMessage response =
await SendPostRequestAsync(
client,
JsonConvert.SerializeObject(request),
path);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return new[] { new ClientQueryResult { StatusCode = HttpStatusCode.NotFound } };
}
var json = await response.Content.ReadAsStringAsync();
List<ClientQueryResult> result =
JsonConvert.DeserializeObject<List<ClientQueryResult>>(json);
foreach (ClientQueryResult item in result)
{
item.StatusCode = response.StatusCode;
item.ContentType = response.Content.Headers.ContentType.ToString();
}
return result;
}
public static Task<HttpResponseMessage> SendPostRequestAsync(
this HttpClient client,
string requestBody,
string path)
{
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
return client.PostAsync(CreateUrl(path), content);
}
public static string CreateUrl(string path)
{
var url = "http://localhost:5000";
if (path != null)
{
url += "/" + path.TrimStart('/');
}
return url;
}
}
}

Now I could write the same test like this:

using Autofac;
using Autofac.Extensions.DependencyInjection;
using HotChocolate;
using HotChocolate.Execution;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Snapper;
using Snapper.Attributes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using GraphQL.API;
using Xunit;
using Xunit.Abstractions;
namespace GraphQL.Tests {
public class GraphQLTests : IClassFixture<SelfHostedApi> {
public HttpClient Sut { get; }
public GraphQLTests(SelfHostedApi fixture)
{
Sut = fixture.CreateDefaultClient();
}
[Theory]
[InlineData("zoekLandbouwers")]
public async Task Queries_Succeed(string queryName)
{
//Arrange
var query = File.ReadAllText($"GraphQLQueries\\{queryName}.graphql");
// act
var result = await Sut.PostAsync(new ClientQueryRequest { Query = query });
// assert
result.ShouldMatchChildSnapshot(queryName);
}
}
}

Popular posts from this blog

Kubernetes–Limit your environmental impact

Reducing the carbon footprint and CO2 emission of our (cloud) workloads, is a responsibility of all of us. If you are running a Kubernetes cluster, have a look at Kube-Green . kube-green is a simple Kubernetes operator that automatically shuts down (some of) your pods when you don't need them. A single pod produces about 11 Kg CO2eq per year( here the calculation). Reason enough to give it a try! Installing kube-green in your cluster The easiest way to install the operator in your cluster is through kubectl. We first need to install a cert-manager: kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.5/cert-manager.yaml Remark: Wait a minute before you continue as it can take some time before the cert-manager is up & running inside your cluster. Now we can install the kube-green operator: kubectl apply -f https://github.com/kube-green/kube-green/releases/latest/download/kube-green.yaml Now in the namespace where we want t...

Azure DevOps/ GitHub emoji

I’m really bad at remembering emoji’s. So here is cheat sheet with all emoji’s that can be used in tools that support the github emoji markdown markup: All credits go to rcaviers who created this list.

.NET 9 - Goodbye sln!

Although the csproj file evolved and simplified a lot over time, the Visual Studio solution file (.sln) remained an ugly file format full of magic GUIDs. With the latest .NET 9 SDK(9.0.200), we finally got an alternative; a new XML-based solution file(.slnx) got introduced in preview. So say goodbye to this ugly sln file: And meet his better looking slnx brother instead: To use this feature we first have to enable it: Go to Tools -> Options -> Environment -> Preview Features Check the checkbox next to Use Solution File Persistence Model Now we can migrate an existing sln file to slnx using the following command: dotnet sln migrate AICalculator.sln .slnx file D:\Projects\Test\AICalculator\AICalculator.slnx generated. Or create a new Visual Studio solution using the slnx format: dotnet new sln --format slnx The template "Solution File" was created successfully. The new format is not yet recognized by VSCode but it does work in Jetbr...